In C++11 we are guided in some cases to pass objects by value and in others by const-reference. However, this guideline depends on the implementation of the method, not just on its interface and intended usage by its clients.
When I write an interface, I do not know how it will be implemented. Is there a good rule of thumb for writing method signatures? For example - in the following code fragment, should I use Bar1
or Bar2
?
class IFoo
{
public:
virtual void Bar1(std::string s) = 0;
virtual void Bar2(const std::string& s) = 0;
};
You can stop reading here if you agree that the correct signature depends on the implementation. Here is an example that shows why I believe so.
In the following example, we should pass the string by value:
class Foo
{
std::string bar;
Foo(std::string byValue)
: bar(std::move(byValue))
{
}
};
Now we can instantiate Foo in an efficient manner in all cases:
Foo foo1("Hello world"); // create once, move once
Foo foo2(s); // the programmer wants to copy s. One copy and one move
Foo foo3(std::move(t)); // the programmer does not need t anymore. No copy at all
In other cases we prefer to pass objects by const reference. For example, in the following case we never want to copy/store the argument, just use its methods:
void DoStuff(const std::string& byRef)
{
std::cout << byRef.length() << std::endl;
}
All possible usages of the above method are already as efficient as possible.
Update
I believe I forgot to show the issues with the const-reference alternative. If the above class Foo
was implemented this way:
class Foo
{
std::string bar;
Foo(const std::string& byRef)
: bar(byRef)
{
}
};
Then we would have the following results:
Foo foo1("Hello world"); // Here we would have one more copy of the string. It is less efficient.
Foo foo2(s); // One copy, like before
Foo foo3(std::move(t)); // Irrelevant here.
Alex.
Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.
'virtual function' means a member function where the specific implementation will depend on the type of the object it is called upon, at run-time. The compiler and run-time support of the language contrive to make this happen. The keyword 'virtual' in C++ was taken from Simula, which had impressed Bjarne Stroustrup.
Also, you can have an implementation in a virtual method, i.e., virtual methods can have implementations in them. These implementations can be overridden by the subclasses of the type in which the virtual method has been defined.
A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&. This topic builds upon the following C++ class, MemoryBlock , which manages a memory buffer.
There's no "theory of everything" here. You got it right, there's a problem. I remember confronting it myself a while back.
My conclusions started here:
If your clients are developers, this job is much harder. Not only is it harder, but there are no clear guidelines. Great framework designers got their prestige because they happened to take risks that paid off. At the same time, in an alternate universe, their risks could have not paid off. That's because appreciating a framework depends on the direction of its growing usage, and subjective opinions which are much harder to reason about than in the application domain.
So there's no clear cut answer in this case. Fortunately, I think you're interested mainly in Application development here. So let's get on to that.
This makes a huge difference. Because we're supposed to have a much better idea of where the system is going, and what kind of code could turn out to be useful. We're not prophets, but at the same time this assumption allows us to give more credit to our intuition, which is based on our knowledge of the requirements, and the needs of our customers (at least as much as we were able to understand).
At this point, we can still divide this into 2 cases:
There are cases where it is beneficial, or even necessary, to define abstraction ahead of the implementation. In cases like this, one has to realize that much more research about the problem is required before defining the abstraction properly. For example, is the domain synchronous or asynchronous? Serial or parallel? High or low level? And other much more concrete questions.
Some extreme agilers will have you believe that you can just write some code and fix it later. However, that claim is falsified very easily once reality hits. If you find hope in it, I encourage you to test it yourself and report if you made any significant discovery. My personal experience, and thought that I have tried putting into the matter, suggest that in big projects this approach is very problematic.
The conclusion in this case is that, if you indeed need to define abstraction ahead, then you should already have a very good idea of the implementation. The better idea you have about it, the higher the chance it will succeed in actually being a proper abstraction.
This is my default choice. It has been said in many ways. "Frameworks should be extracted", "Extract 'till you drop", and even "Convention over Configuration" has some similarities in concept.
Basically this means that you implement your required components as necessary, but keep a sharp eye on what's going on. The trick here is to look out for chances to abstract in ways that actually benefit you practically in terms of development and maintenance.
This often comes up as a class that does what you want, but more. In which case, you abstract the intersection away into a more general case. You repeat this process as necessary throughout development.
It's important to not get caught up and still keep your feet on the ground. I've seen many abstraction attempts go wrong to a point where there's no way to reason about its name and deduce its intent except reading thousands of lines of code that use it. For example, in the current code base I'm working on, the type which should have been called Image
is called BinaryData
. All across the code are attempts to treat it as a concrete (Image), and as an abstract concept at the same time.
As I always remind myself, the best best practice you can have is to tame known best practices to fit your problem, rather than the other way around. If you can't do that, well, maybe the problem is interesting enough to require further attention, and a bit of original thought.
You could also provide an overload for Bar2
that takes an rvalue reference:
class IFoo
{
public:
virtual void Bar2(const std::string& s) = 0;
virtual void Bar2(std::string&& s)
{
Bar2(s); // calls the const& overload because s is an lvalue
}
};
By default, the rvalue reference overload simply calls the const lvalue reference overlad. But if a specific subclass can take advantage of rvalue references, the rvalue reference overload can be overriden.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With