Why Override Operator()

Why override operator()?

One of the primary goal when overloading operator() is to create a functor. A functor acts just like a function, but it has the advantages that it is stateful, meaning it can keep data reflecting its state between calls.

Here is a simple functor example :

struct Accumulator
{
int counter = 0;
int operator()(int i) { return counter += i; }
}
...
Accumulator acc;
cout << acc(10) << endl; //prints "10"
cout << acc(20) << endl; //prints "30"

Functors are heavily used with generic programming. Many STL algorithms are written in a very general way, so that you can plug-in your own function/functor into the algorithm. For example, the algorithm std::for_each allows you to apply an operation on each element of a range. It could be implemented something like that :

template <typename InputIterator, typename Functor>
void for_each(InputIterator first, InputIterator last, Functor f)
{
while (first != last) f(*first++);
}

You see that this algorithm is very generic since it is parametrized by a function. By using the operator(), this function lets you use either a functor or a function pointer. Here's an example showing both possibilities :

void print(int i) { std::cout << i << std::endl; }
...
std::vector<int> vec;
// Fill vec

// Using a functor
Accumulator acc;
std::for_each(vec.begin(), vec.end(), acc);
// acc.counter contains the sum of all elements of the vector

// Using a function pointer
std::for_each(vec.begin(), vec.end(), print); // prints all elements

Concerning your question about operator() overloading, well yes it is possible. You can perfectly write a functor that has several parentheses operator, as long as you respect the basic rules of method overloading (e.g. overloading only on the return type is not possible).

Can I override operators in C++?

An overloaded operator is just a function, so it can be virtual, and overridden.

But it's seldom a good idea.

Consider an overridden copy assignment operator, that in some derived class checks whether the value to be assigned is compatible with the object assigned to. Effectively it has replaced static type checking with dynamic type checking, which involves a lot of laborous testing and only a statistical chance of correctness.


Example of ungoodness:

#include <assert.h>
#include <iostream>
#include <string>
using namespace std;

struct Person
{
string name;

virtual
auto operator=( Person const& other )
-> Person&
{ name = other.name; return *this; }

Person( string const& _name ): name( _name ) {}
};

struct Employee: Person
{
int id;

auto operator=( Person const& other )
-> Person&
override
{
auto& other_as_employee = dynamic_cast<Employee const&>( other );

Person::operator=( other );
id = other_as_employee.id;
return *this;
}

auto operator=( Employee const& other )
-> Employee&
{
return static_cast<Employee&>(
operator=( static_cast<Person const&>( other ) )
);
}

Employee( string const& _name, int const _id )
: Person( _name )
, id( _id )
{}
};

void foo( Person& someone )
{
someone = Person( "Maria" ); // Fails, probably unexpectedly.
}

auto main() -> int
{
Person&& someone = Employee( "John", 12345 );
foo( someone );
}

Why is it called operator overloading?

Consider the following code:

int x;
Foo y;
&x; // built-in functionality
&y; // y.operator&();

We have two variables of different types. We apply the same & operator to both of them. For x it uses the built-in address-of operator whereas for y it calls your user-defined function.

That's exactly what you're describing as overloading: There are multiple functions (well, one of them is the built-in functionality, not really a "function") and they're selected based on the type of the operand.

Why and how to overload operator for printing

For question 1, your understanding is correct, but the real improvement comes from question 2's suggestion of writing:

template <typename T>
friend std::ostream& operator<<(std::ostream&, Mystack<T> const& );

That will let anybody stream objects of your type in the same way they would stream anything else:

std::cout << "Hi, my stack is " << stack << ", it has size " << stack.size();

to any stream they want:

some_file << "Result of computation is: " << stack;
std::cerr << "Error, invalid stack: " << stack << ", expected: " << some_other_thing;

C++ overload assignment operator

The line SpecialFloat f = 1.0f; cannot perform assignment from 1.0f to f because f doesn't exist yet. We are just creating it.

It would do if you had written SpecialFloat f{0.0f}; f = 1.0f [Demo].

The line SpecialFloat f = 1.0f; is doing copy initialization (1).

Initializes an object from another object.

Syntax

T object = other; (1)

In your code T is SpecialFloat, a class type, and other is a float (not T or derived from T).

The effects of copy initialization are:

...

If T is a class type, and the cv-unqualified version of the type of other is not T or derived from T [...] user-defined conversion sequences that can convert from the type of other to T are
examined and the best one is selected through overload resolution. The result of the conversion, which is a rvalue temporary [...] of the cv-unqualified version of T if a converting constructor was used, is then used to direct-initialize the object.

User-defined conversions from float to SpecialFloat should be examined. However, explicit constructors are not considered for copy-initialization.

Notes

Copy-initialization is less permissive than
direct-initialization: explicit constructors are not converting
constructors and are not considered for copy-initialization.

One way to solve this is to use direct initialization, and, if possible, with braces instead of parentheses, i.e. SpecialFloat f{1.0f}; [Demo].
There's a C++ Core Guideline advising about preferring the {}-initializer syntax.
Also, declaring single-argument constructors explicit is a general recommendation, so I would keep the user-declared constructor as explicit.

Another way would be to make SpecialFloat class an aggregate, by removing the user-declared constructor, and use aggregate initialization, SpecialFloat f = {1.0f}; [Demo].

Finally, as commented by others, notice the signature of the assignment operator is SpecialFloat& operator=(const float f), what indicates that a SpecialFloat& has to be returned. So first, update the object with m_float = f;; then, return it with return *this;.


[Edit]

I just came accross this article from Arthur O'Dwyer's, The Knightmare of Initialization in C++ where he basically favours copy initialization over direct initialization with braces, in order to improve the readability of the code.

Simple guidelines for variable initialization in C++:

  • Use = whenever you can.
  • Use initializer-list syntax {} only for element initializers (of containers and aggregates).
  • Use function-call syntax () to call a constructor, viewed as an object-factory.

Thus:

int i = 0;
std::vector<int> v = {1, 2, 3, 4};
Widget w(name, price, quantity);

Moreover, he suggests to combine the copy initialization with the Almost Always Auto style. Going back to the original OP's question, that would allow us to keep the SpecialFloat class untouched and write auto f = SpecialFloat{1.0f}; [Demo].

He acknowledges though that his guidelines conflict with the aforementioned C++ Core Guideline of preferring the {}-initializer syntax.

Overloading operator new for other class

My thanks to François Andrieux for his timely answers. The short answer to my question is to create a factory pattern to create Message objects. Something like :

(Node.cpp)

unique_ptr<Message> Node::createMessage(size_t capacity) {
return unique_ptr(new Message(capacity + header_size));
}

That way, the user doesn't have to precise the header size every time they want to create a new message object. The downside of this is that the user has to know the exact method to use. Forcing the user to use this method is possible if the Message natural constructor is made inaccessible.

Overloading the operator new is not the answer, and the justification is the following :

There are only two options for overloading operator new. You can overload the global operator new or the member operator new. The global operator new will impact all uses of new in the current translation unit, so we can ignore that one. It won't be useful here. The other option is overloading operator new for a specific class as a member operator. However, that only changes how allocating that class is done. If you overload Node::operator new then you only change what happens when you do new Node. You can't change Message::operator new from within Node

Another possibility to allocate more space than the natural constructor would do is to use placement new. However this makes construction and destruction over-complicated which is not the intended objective here. Also, it would be less risky to let the user write new Message(header_size + capacity) every time than let him allocate and free memory.



Related Topics



Leave a reply



Submit