简体   繁体   中英

C++ Encapsulation and references in constructor

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:

  1. namePassed is copied to the argument _name .

  2. The argument _name is copied to the member name .

  3. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM