简体   繁体   中英

C++: Passing object to class constructor, how is it stored?

Consider the following example of a simple class implementation in C++.

foo.hpp

#include <vector>
class Foo {
private:
    std::vector<double> X;
public:
    Foo() = default;
    ~Foo() = default;
    Foo(std::vector<double>&);
};

foo.cpp

#include "Foo.hpp"
Foo::Foo(std::vector<double>& X):
        X(X)
{}

In this case, the vector X is passed by reference to the constructor of the class Foo. I start having doubts, though, on whether the operation X(X) in the implementation makes a copy and pastes it "in" the member X of the class.

Which is it?

Yes, the data member X will be copy-initialized from the constructor parameter X .

If you declare the data member X as reference, then no copy operation happens. Eg

class Foo {
private:
    std::vector<double>& X;
public:
    ~Foo() = default;
    Foo(std::vector<double>&);
};

Foo::Foo(std::vector<double>& X):
        X(X)
{}

Then

std::vector<double> v;
Foo f(v); // no copies; f.X refers to v

Rest assured. The member's type is vector<double> , so the compiler will look for a constructor overload in the vector<double> class that matches the provided argument type ( vector<double>& ).

The best match it will find is the const vector<double>& overload - the copy constructor.

As an alternative to this, you can create a constructor with an rvalue reference for the class:

#include <vector>
class Foo {
private:
    std::vector<double> X;
public:
    Foo() = default;
    ~Foo() = default;
    Foo(const std::vector<double>&);
    Foo(std::vector<double>&&);
};

Then the constructor would be implemented like this:

Foo::Foo(std::vector<double>&& X_) : X(std::move(X_)) {}

No copy is done when this constructor is invoked.

In your example, X (the member of the Foo class) is copy-constructed . One solution to prevent the copy would be defining X as an lvalue reference in the Foo class, though it is not a general solution for all cases, and it can be dangerous (result in a dangling reference) if the scope of the original X is finished when Foo is still alive, All in all, you have one of the below options depending on your application

  1. Using lvalue reference to X, if the scope of each possible Foo object is lower than the passed object (X):

    foo.hpp

     #include <vector> class Foo { private: std::vector<double> &X; public: Foo() = default; ~Foo() = default; Foo(std::vector<double>&); };

    foo.cpp

     #include "Foo.hpp" Foo::Foo(std::vector<double>& X): X(X) // keep the lvalue refrence to the passed Object {}

Then

std::vector<double> v;
Foo f(v); // no copies; f.X refers to v
// Foo function calls including f.X should be finished before destruction of the v Object
  1. Cast X to an rvalue reference (std::move) and move it to the member X of Foo class, if X contents are not supposed to be usable through the passed Object name after the Foo constructor call:

    foo.hpp

     #include <vector> class Foo { private: std::vector<double> X; public: Foo() = default; ~Foo() = default; Foo(std::vector<double>&&); };

    foo.cpp

     #include "Foo.hpp" Foo::Foo(std::vector<double>&& X): X(X) // move constructor of std::vector<double> is called in this line {}

Then

std::vector<double> v = {1.0, 2.0, 3.0}; 
Foo f(std::move(v)); // no copies; f.X use resource of v, but v is empty now
// v is empty now
  1. The last method is exactly the same as your implementation. In this case: foo.hpp

     #include <vector> class Foo { private: std::vector<double> X; public: Foo() = default; ~Foo() = default; Foo(const std::vector<double>&); };

    foo.cpp

     #include "Foo.hpp" Foo::Foo(const std::vector<double>& X): // using const is better: in this case X(X) // copy constructor of std::vector<double> is called in this line {}

Then

std::vector<double> v = {1.0, 2.0, 3.0};
Foo f(v); // do copies; f.X copy the resource of v, and v is still valid

Having said that, all of these methods have their own applications depending on the situation. For the sake of clarity, just simply apply this prioritizing rule:
1- pass by rvalue reference , which results in move constructor for the member. (eg, when you want to pass an object to the std::thread constructor, and so the ownership, meaning the resources, and so resources lifetime.).
2- pass the lvalue reference and keep it, when the passed object lifetime (v in these examples) is greater than the referring one.
3- pass const lvalue reference and let the copy constructor be called ( copy constructor of std::vector in these examples), when the last choices are not applicable, either due to the further use of the passed object or due to the lifetime of the passed Object.

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