I'm having a trouble putting together the concept of encapsulation (no one else should change the object data directly) and good performance recommendations when someone pass parameters.
For this code
// Code only for illustration, I'm not looking for syntax comments (thanks)
class Person{
private:
std::string name;
std::string Id;
public:
Person(std::string _name, std::vector<double> vec)...
I get that the most recommended thing to use is const
references for parameters (even the compiler warns that). But this let the objects depending on the code that call them?
In the code above, if I pass by reference the vector
, and the object needs to create a copy of that, or modify, don't be passing by value the same?. What happen with the object when someone else modify that referenced vector?
Hope this is readable
If you have a class
class Person{
std::string name;
public:
Person(std::string _name){
//Body of constructor
}
};
then there are two strings accessible in the body of the constructor: the member name
of the instance of Person
being constructed, and the parameter _name
of the function. Usually, what the constructor would do would be something like*
Person(std::string _name){
name = _name;
}
which says "copy that value of _name
into name
" - and which has the desired behavior that name
is an object independent of anything else.
The trouble with this is that if we somewhere else wanted to construct an instance of Person
and already had a string in mind - for instance we wrote the code
std::string namePassed = "Alice";
Person person(namePassed);
the compiler would then have to generate code to pass namePassed
by value - which means that the compiler will copy namePassed
into a new location which will be referenced by _name
inside the body of the constructor. That means that the whole sequence of events that happens for construction is as follows:
namePassed
is copied to the argument _name
.
The argument _name
is copied to the member name
.
The argument _name
is destructed.
What we really wanted to happen is that namedPassed
would simply be copied** to the member name
- we don't want to make a temporary copy for no reason!
The solution to this is to pass _name
by const reference as
Person(std::string const& _name){
name = _name;
}
because then when we write
std::string namePassed = "Alice";
Person person(namePassed);
the compiler says that _name
is a reference (essentially a pointer) to namePassed
and then copies to name
from that reference - meaning that the member name
still ends up with an independent copy, but we avoided the extra copying operation. If we later assigned to namePassed
, that would be okay because the member name
is a copy, sharing no state with namePassed
and the parameter _name
would have left scope***. The const&
qualification only applies to the temporary parameter, not to member.
If you wanted to store a member reference, you would have to declare the member as a constant reference, not just the parameter. That would look like this:
class Person{
std::string const& name;
public:
Person(std;:string const& _name):name(_name){}
};
This is a bad idea outside of a few niche situations, since now the reference to the string must be alive as long as the instance of Person is and changes to the referenced string will be reflected in the class - but note that the member being stored is marked const&
not just the parameter.
*The code here is not idiomatic since I wanted to emphasize the copy operation. More common is to just write
Person(std::string const& _name):name(_name){}
which uses an initializer list. The former code said "default initialize name
(ie initialize an empty string) and then assign a value to it" whereas this code directly invokes the copy constructor for name
with the given argument.
**There's a bit of complication by the move (rvalue) semantics, but that's somewhat beyond the scope of this answer. It's worth noting that there's a fairly common construction where one passes-by-value then moves:
Person(std::string _name):name(std::move(_name)){}
which expresses the idea that we can pass in a value _name
which is guaranteed to be an independent copy of the string. Then, knowing that it's a copy, we can move it into place, which basically means that we steal the memory used by _name
and give it to name
, which is far less expensive than copying the whole string and less annoying than writing separate constructors for rvalues and lvalues.
***It's possible to get errors, however, if the value of namePassed
managed to change during the execution of the constructor, but before the copy was made, then name
could be copied from the wrong source. This is mostly an issue that arises when writing multithreaded code.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.