• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: gcc and a problem with temporaries
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: gcc and a problem with temporaries


  • Subject: Re: gcc and a problem with temporaries
  • From: Howard Hinnant <email@hidden>
  • Date: Mon, 5 May 2008 12:05:16 -0400

On Apr 30, 2008, at 4:51 PM, Scott Thompson wrote:

I've run into an interesting problem while compiling some legacy code with gcc (code that is to be made cross-platform). The solution is perplexing me so I thought I would ask here to see if anyone had ideas. I've boiled the problematic code down to this small sample:

class Owned
{
  public:
    Owned() {};
};

class Inited
{
  public:
    Inited();
    Inited(Owned *owned);
    Inited &operator=(Owned* itemToAssign);

    // Problematic Delcaration
    Inited(Inited &itemToCopy);
};

Owned *makeOwned()
{
  return new Owned;
}

int main (int argc, char * const argv[]) {
  /* ** Compile error here ** */
  Inited myInited = makeOwned();
}

The compile error is:

main.cpp:25: error: no matching function for call to 'Inited::Inited(Inited)'
main.cpp:15: note: candidates are: Inited::Inited(Inited&)
main.cpp:11: note: Inited::Inited(Owned*)
main.cpp:25: error: initializing temporary from result of 'Inited::Inited(Owned*)'


So it would appear that GCC is trying to create a temporary and then initialize the "myInited" from that temporary. I gather that would be roughly equivalent to:

const Inited t1(makeOwned()); Inited myInited(t1);

Because the temporary is const, it can't use the copy constructor with the non-const parameter. But, apparently, the presence of this constructor is what causes it to create the temporary in the first place. If I remove this copy constructor then everything compiles without trouble.

I can also fix the problem by declaring:

Inited(const Inited &itemToCopy); /* note the addition of a const */

While adding that that makes sense for this toy example, it doesn't work in the original code where "Inited" is a "smart pointer" object. The "itemToCopy" is expected to relinquish control of the pointer it contains to the newly initialized object and is, therefore, not const in this context.

I could also change the line of code where the error is reported to:

  Inited myInited(makeOwned());

Again in the toy code this is not really a problem, but the original code base uses the version with the equals sign "all over the place". That code passes through several other C++ compilers without issue, but it won't compile through XCode.

Right now, it looks like a potential solution would be to add the const copy constructor, and make the embedded pointer of the smart pointer class "mutable". But that seems a bit kludgey.

Can anyone suggest a more elegant solution?

You have stumbled into the domain of "move semantics". :-) This is something that will be much better supported in the next C++ standard (which is not yet available). However you can emulate this support today. It won't be as good as C++0X, but it will be close, and it get you headed in the right direction for future maintenance.


In a nutshell, a move is nothing more than a shallow copy, followed by stripping the source of its resources (such as an owned pointer). And it is always safe to move from rvalues (temporaries). It is never safe to move from lvalues, unless you use syntax that doesn't look like a copy. Your class Inited is what will be referred to as a "move- only" class. That is, you can move from it, but you can't copy from it. Once designed correctly with respect to this framework (which is not too hard today, and downright easy in C++0X), your main() will compile just as you have it.

Here's how you can fake it today:

class Owned
{
  public:
    Owned() {};
};

class Inited
{
    Owned* owned_;
  public:
    Inited();
    Inited(Owned *owned) : owned_(owned) {}
    ~Inited() {reset();}

    Inited &operator=(Owned* itemToAssign)
    {
        reset();
        owned_ = itemToAssign;
        return *this;
    }

    // move semantics
    Inited(const Inited &itemToCopy)
        : owned_(const_cast<Inited&>(itemToCopy).release())
    {}

    Inited& operator=(const Inited &itemToCopy)
    {
        if (this != &itemToCopy)
        {
            reset();
            owned_ = const_cast<Inited&>(itemToCopy).release();
        }
        return *this;
    }

    void reset()
    {
        delete owned_;
        owned_ = 0;
    }

    Owned* release()
    {
        Owned* t = owned_;
        owned_ = 0;
        return t;
    }
private:

    // disable copy semantics
    Inited(Inited&);
    Inited& operator=(Inited&);
};

inline
Inited
move(Inited& p)
{
    return Inited(p.release());
}

And here is an example main():

int main () {
  Inited myInited = makeOwned();
//  Inited i2 = myInited;  // purposefully won't compile
   Inited i2 = move(myInited);  // ok, i2 owns it and myInited doesn't
}

In C++0X, the std::lib will supply the move function so you won't have to write it (you'll need to call std::move instead of your hand- written move though).

And your Inited class will change to:

class Inited
{
    Owned* owned_;
  public:
    Inited();
    Inited(Owned *owned) : owned_(owned) {}
    ~Inited() {reset();}

    Inited &operator=(Owned* itemToAssign)
    {
        reset();
        owned_ = itemToAssign;
        return *this;
    }

    // move semantics
    Inited(Inited&& itemToCopy)
        : owned_(itemToCopy.release())
    {}

    Inited& operator=(Inited&& itemToCopy)
    {
        reset();
        owned_ = itemToCopy.release();
        return *this;
    }

    // disable copy semantics
    Inited(const Inited&) = delete;
    Inited& operator=(const Inited&) = delete;

    void reset()
    {
        delete owned_;
        owned_ = 0;
    }

    Owned* release()
    {
        Owned* t = owned_;
        owned_ = 0;
        return t;
    }
};

The "&&" is called a "rvalue reference", and attracts rvalue arguments (temporaries). lvalues will be attracted to the "normal" copy ctor and assignment, and will subsequently fail to compile (as desired).

This is all there really is, but if you would like to read more about it, there's a brief intro here:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html

Hope this helps.

-Howard

_______________________________________________
Do not post admin requests to the list. They will be ignored.
Xcode-users mailing list      (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden


  • Prev by Date: Re: Xcode 3.0 debugger console - history missing?
  • Next by Date: Compile webkit frameworks for embedding
  • Previous by thread: Re: How to open source files in multiple windows?
  • Next by thread: Compile webkit frameworks for embedding
  • Index(es):
    • Date
    • Thread