Copy ctor

Hi folks,

I am writing a copy constructor for this class:
1
2
3
4
5
6
class Widget {
public:
    Widget(const Widget& src) :name(src.name) { }    
private:
    std::string& name;   // I want to keep this unchanged
}

calling the copy ctor results in that this.name is another name of src.name, namely: there is a single identical string shared between both of them. How can I make this.name to have an IDENTICAL COPY of src.name for this class?
Last edited on
closed account (1yR4jE8b)
If you want it to be a copy of the string, then don't make name a reference to a string, make it just a plain old string. The std::string classes copy constructor will take care of the rest.
You've got it. The only thing you are missing is the fact that references cannot be null.

If you added a constructor that takes a string reference and initialize name with it, your class will work as expected. The downside is that the original string cannot be owned by any of the Widgets. You'd have to do something like this:
1
2
3
4
5
6
7
8
9
10
// string is local to this scope
string s = "asdf";

Widget w1( s );
Widget w2( s );

// assuming name was made public for simplicity
w1.name = "ok";

cout << w2.name;









ok


If you want one Widget to create a string name and copies of that Widget to refer to its name, you could use a pointer on line 5 and just create a constructor and destructor that manage the sole instance.
Last edited on
If you want one Widget to create a string name and copies of that Widget to refer to its name, you could use a pointer on line 5 and just create a constructor and destructor that manage the sole instance.

Regarding this, consider shared_ptr for a better implementation:
http://www.boost.org/doc/libs/1_41_0/libs/smart_ptr/shared_ptr.htm

One of my pet peeves with C++ is that any class that contains a reference (const or not)
as one of its data members is automatically NOT assignable. (but it can still be copy
constructible). The former is the case because references cannot be rebound.

Just a word of caution re: shared_ptr: it is orders of magnitude slower than references
and still much slower than other things like auto_ptr. Having said all that, shared_ptr
might be what you need.

One of my pet peeves with C++ is that any class that contains a reference (const or not)
as one of its data members is automatically NOT assignable. (but it can still be copy
constructible). The former is the case because references cannot be rebound.


Can you be more specific? I'm not familiar with this restriction.

If I recall a recent post of yours correctly, "copy and swap" was the way to go with assignment operators (for thread saftey?). In this case, I suppose the reference would have to be initialized prior to that operation; but what makes it not assignable?
Last edited on
error: non-static reference member ‘int& Foo::bar’, can't use default assignment operator


Classes with references are also generally not default constructable, which means you cannot put them in containers. But that's just the nature of the beast. You cannot have unbound references and you cannot rebind references.

Using references the way the OP tried is generally unsafe anyway. That class now has an external dependency on the lifetime and value of that string.
@moorecm:

The copy/swap idiom for assignment operators is for exception safety.
Although the default assignment operator would not work, you could still define one that does. At least, this seems to work:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

struct Widget {
    string & name;
    Widget( string & s ) : name(s) {}
    Widget( const Widget & src ) : name(src.name) {}
    Widget & operator=( Widget w ) { swap( name, w.name ); }
};

int main( int argc, char * args[] )
{
    string a = "ok";
    string b = "asdf";

    Widget w1( b );
    Widget w2( b );

    // changed reference
    w1.name = a;

    // assignment
    w2 = w1;

    // copy
    Widget w3( w1 );

    cout << w1.name << endl;
    cout << w2.name << endl;
    cout << w3.name << endl;

    return 0;
}



























ok
ok
ok


The above class is not DefaultConstructable but wouldn't this then be considered Assignable?
moorecm wrote:
At least, this seems to work:


You might want to inspect the values of "a" and "b" before you make that claim. Line 20 has the affect of "b = a". Most folks would find that rather surprising, especially if Widget was defined elsewhere.

Try sprinkling a few of these in your code:

1
2
    assert(a == "ok");
    assert(b == "asdf");
That's correct; I should have separated the three cases and tested it more thoroughly.
In general, objects should only hold const references if they hold any references at all. That would have prevented line 8 from compiling and the cause of the surprising behavior. I typically find that holding references at all in a class is a http://en.wikipedia.org/wiki/Code_smell . [I really wish cplusplus.com supported all BBCode tags, but especially the url tag.]

Your original advice was absolutely correct. If you think you need to hold a reference, you probably want a shared_ptr instead. jsmith's "order of magnitude" performance difference is a bit of a stretch. There is a cost for for using them to be sure. This shows a much more nuanced picture: http://www.boost.org/doc/libs/1_41_0/libs/smart_ptr/smarttests.htm

One of the few cases I allow myself to use member references is in functors that are intended to be used with algorithms where a) you can be certain of the lifetime of the referent and b) that the referent does not change. This is still dangerous as the functor could be unintentionally misused by a maintenance programmer (which may well be me) well after its written.
So in summary, references cannot be rebound once initialized in the sense that the memory location that they point to cannot be changed. You can conceptually change what a reference is pointing to by changing what is actually located in its "pointed-to" memory location. (I find this particularly difficult to describe in words--this statement must be read very carefully.)

Is this correct?

PS: I guess I never made this realization--references make the above all too transparent. I love threads where I learn something!
Last edited on
So in summary, references cannot be rebound once initialized in the sense that the memory location that they point to cannot be changed.
No. In the sense that once initialized, they can't be made to point to somewhere else. If you have objects a and b, and a reference c initialized to a, c cannot later be made to point to b.
moorecm wrote:
You can conceptually change what a reference is pointing to by changing what is actually located in its "pointed-to" memory location.


Nope. There is no guarantee whatsoever that there is a "pointed to" memory location. References are not pointers. Thinking of them this way will lead to trouble. The compiler frequently optimizes references completely away.

To clarify further -- references are another name for an object, nothing more. And once you tell the compiler that "a" means "b", you cannot tell it later that "a" means "c". The concept is explained very well here: http://www.cprogramming.com/tutorial/references.html
Last edited on
Thinking of them this way will lead to trouble.
For example? Not being sarcastic, I'm just curious.
To be more specific, I should have said that you can change the value of the variable being referred to.
@helios: It is because it leads to a mistaken notion of what a reference is, what it means, and how it can be manipulated.

int A;
int& B = A;

B means A. B does not "point to" A. It is purely symbolic.

If you think of references as pointers, then vector<int&>, for example, might make sense. But an int& is just an alias, another name. vector<int&> contains orthogonal concepts that have no meaning at all. Like "drinking software" or "hugging dreams".
Yes, it makes sense if this is your first day using references. Everyone knows you can't have arrays of references.

The compiler frequently optimizes references completely away.
It is purely symbolic.
I don't know. I prefer to assume that they weren't optimized and that they're just pointers in disguise.
There are cases where the compiler may hold a reference as a pointer. But that's something I shouldn't care about or think about, in the same sense that I don't care whether a compiler is holding an automatic variable in a register or on the stack or not at all.

The details of how the compiler implements it only matter to me when I have to break out the debugger for a bug that only rears its head when compiled -O3. :-)

By thinking of them as nothing more than symbolic references, my brain is less likely to misinterpret how references can and should be used.
Topic archived. No new replies allowed.