idlackage
idlackage

Reputation: 2863

Storing a member variable from a const reference parameter

By value:

class Test {
private:
    HugeObject h; // Copy 1
public:
    void SetObject(HugeObject hugeObject) { // Copy 2
        h = hugeObject;                     // Copy 3
    }
}

// Somewhere else
Test t();
t.SetObject(HugeObject());

This is bad because three implicit copies are created. If I change the parameter of SetObject() to const HugeObject& hugeObject, it's also bad because I'd be storing something that will no longer exist outside that function's scope.

So, to prevent copying twice and storing invalid data, I can copy just twice instead:

void SetObject(const HugeObject& hugeObject) { 
    h = HugeObject(hugeObject); // Copy constructor
}

Is this a valid way of handling this situation or am I misunderstanding something? A silly optimization or not an optimization at all? Are there better ways, aside from storing a pointer?

Upvotes: 1

Views: 1888

Answers (3)

settwi
settwi

Reputation: 348

When you declare h first in the class, what you're actually doing is calling the object's default constructor, which is implicitly called upon creation of an object that doesn't use an explicit syntax; that is,

HugeObject h; // Calls default constructor implicitly.
HugeObject h2 = HugeObject(); // Still calls default constructor.
HugeObject ougeHobject = HugeObject(h); // Calls copy constructor.
h2 = ougeHobject; // calls HugeObject's operator=

The last part is what I'm really getting at. When you assign h to hugeObject in SetObject, you're actually using HugeObject's operator=. This is not the same thing as construction. If operator= isn't defined by a class, then the C++ implementation synthesizes it; this default works by assigning each member of the right-hand-side object of a basic type (double, for instance,) to the corresponding member on the left-hand-side object. For class types stored as variables of hugeObject, their corresponding operator=s are called.

To answer the question directly now, if you'd like to limit the copies you make, just assign h to a reference of hugeObject in SetObject, as both have already been instantiated:

void SetObject(const HugeObject& hugeObject) {
    h = hugeObject;
}

Although technically out of scope, the const HugeObject&'s values are copied via HugeObject's operator= to h. This does not implicitly construct a new object, as both have already been instantiated; again, it just calls operator=.

Upvotes: 2

NathanOliver
NathanOliver

Reputation: 180980

Why not get the object during construction? Then you get the original creation and a copy.

#include <iostream>

using std::cout;

class Huge 
{
public:
    Huge() { cout << "constructor\n"; }
    Huge(const Huge & h) { cout << "copy\n"; }
    Huge & operator = (const Huge & h) { cout << "assignment\n"; return *this; }
};

class Holder
{
    Huge h;
public:
    Holder(const Huge & h_) : h(h_) {};
};

int main()
{
    Huge bar;
    Holder foo(bar);
}

Output:

constructor
copy

Live Example

Upvotes: 1

Barry
Barry

Reputation: 303576

This is bad because three implicit copies are created.

Actually, only one copy happens in that situation. The HugeObject() temporary is constructed in-place as the argument hugeObject, and then it is copied into h from there.

If I change the parameter of SetObject() to const HugeObject& hugeObject, it's also bad because I'd be storing something that will no longer exist outside that function's scope.

That's not accurate either - you're still copying into h, you're not storing a reference to it. That would save you a copy in the case of:

HugeObject h;
t.SetObject(h); // copy from h into t.h, no copy necessary
                // into the argument of SetObject()

Taking hugeObject by const& is the best way to do it in C++03. If you have access to a C++11 compiler, and your HugeObject is efficiently movable, you should additionally add an rvalue overload:

void setObject(HugeObject&& hugeObject) {
    h = std::move(hugeObject);
}

Upvotes: 2

Related Questions