views:

728

answers:

7

I'm trying to create am immutable type (class) in C++,

I made it so that all methods "aka member functions" don't modify the object and return a new instance instead.

I'm running across a bunch of issues, but they all revolve around the reference types in C++.

One example is when passing parameters of the same class type by reference:

Imm Imm::someOp( const Imm& p_im ) const
{
   ...
   p_im = p_im.someOtherOp(); //error, p_im is const, can't modify it!
   ...
}

The error is caused by passing the value by reference. If instead, I was passing the reference by value, then the error line above would not be an error!

Consider a Java/C# example

class Imm
{
    ...
    Imm someOp( Imm p_im )
    {
        ....
        p_im = p_im.someOtherOp(); //ok, you're not modifying the 
                 //original object, just changing the local reference
        ....  
    }
    ....
}

How can I do something like this in C++? I know I can use pointers but then I run into the whole memory management mess. I don't want to worry about who owns references to objects.

Ideally I'd like to design the class to be like immutable strings in python; you can use them without ever noticing or even knowing that they're immutable, and they just behave as you expect; they just work.

EDIT

Of course I can get around it by passing-by-value or by using a temp variable (which is what I'm doing currently). What I'm asking about is "how to pass references by value in C++"

I'm expecting the answer to revolve around something in the STL, I'm currently looking into smart_ptr family of templates.

UPDATE

Thanks for the responses, I realize there's no escape from pointers. (see my other question, which is really a follow up on this one)

+1  A: 

Didn't you forget to set the method you call as const?

EDIT: so, with the const fixed.

Maybe you should do something like

Imm & tmp = p_im.someOtherOp();

Then do further operation on the tmp variable.

If you set a variable or a parameter as const & you just cannot assign to it.

Julian Aubourg
+4  A: 

Isn't assignment, by definition, not a constant operation?

You look like you're trying to assign something to a const reference, which totally defeats the idea of a const reference.

I think you may be looking for a pointer instead of a reference.

"totally defeats the idea of const reference", I kinda figured that after the fact. It seems I was thinking in terms of Java-style references.
hasen j
Those are called pointers in C++.
Eclipse
A: 

You need to make a new copy of your incoming argument. You can do what you want in several equivalent ways: 1) you could pass-by-value:

Imm Imm::someOp( Imm im ) const {
   im = im.someOtherOp();      // local im is a copy, original im not modified
   return im;                  // return by value (another copy)
}

or 2) you can pass-by-reference and make a copy explicitly:

Imm Imm::someOp( const Imm & im ) const {
   Imm tmp = im.someOtherOp(); // local tmp is a copy
   return tmp;                 // return by value (another copy)
}

both forms are equivalent.

jwfearn
Not exactly equivalent. For the first example, you have one object using the assignment operator/copy constructor. For the other, you have two objects and are using the constructor (I think). You would EXPECT them to be equivalent, but this always isn't the case.
strager
did you forget to declare tmp?
rlbond
@rlbond, thanks, i fixed it
jwfearn
+4  A: 

In Java and C#, you are not really dealing with a reference - they are more like handles or pointers. A reference in C++ is really another name for the original object, not a pointer to it (although it may be implemented with a pointer). When you assign a value to a reference, you are assigning to the object itself. There is confusion in that to initialize a reference you can use the = character, but it is an initialization, not an assignment.

 Imm im, im2, im3; 
 Imm &imr = im;  // initialize a reference to im
 imr = im2; // assign im2 to imr (changes the value of im as well)
 Imm *imp = &im; // initialize a pointer to the address of im
 imp = &im3; // assign the address of im3 to imp (im is unnaffected);
 (*imp) = im2; // assign im2 to imp (modifies im3 as well).

If you specifically want to pass "references by value" then you are essentially asking for a contradition in terms. References, by definition are passed by reference. As pointed out elsewhere, you can pass a pointer by value, or else a straight value. If you really want, you can hold onto a reference in a class and pass that around by value:

 struct ImmRef
 {
     Imm &Ref;
     ImmRef(Imm &ref) : Ref(ref) {}
 };

Note also that a const applied to a reference is making the referred to object constant, not the reference. References are always const.

Eclipse
So how would the OP solve his problem? Include that in your answer.
strager
+3  A: 

It doesn't work like that in C++.

When you pass a reference to an object, you are actually passing the address in memory of the object. References can't be re-seated to other objects either, hence the C++ adage "the reference IS the object." You HAVE to make a copy to modify it. Java will do this behind the scenes for you. C++, you just have to copy it.

rlbond
A: 

Check this to know about temporary lifetime http://herbsutter.wordpress.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Imm Imm::someOp( const Imm& p_im ) const
{
   ...
   //Imm& im = p_im.someOtherOp();       // will *not* work
   const Imm& im = p_im.someOtherOp();   // will work, but you get a const reference
   ...
}

But you can use boost::shared_ptr

shared_ptr<Imm> Imm::someOtherOp() const
{
  shared_ptr<Imm> ret = new Imm;
  ...
  return ret;
}

shared_ptr<Imm> Imm::someOp(const share_ptr<Imm>& p_im) const
{
  shared_ptr<Imm> im = p_im->someOtherOp();
}
Ismael
A: 

I think this is about the same as jwfearn's answer as it uses a temporary variable:

class String
{
    const std::wstring m_string;
public:
    String() {}
    String(const wchar_t* pStr) : m_string(pStr) {}
    String(const std::wstring& str) : m_string(str) {}
    String(const String& str) : m_string(str.m_string) {}

    String Append(const String& right) const {
     const std::wstring result = m_string + right.m_string;
     return String(result);
    }
};

How would you like it to be different?

Dan