Object that transfers ownership on assignment/copy

S

Scott Gifford

As a possible solution to a problem I'm trying to solve with an
iterator (see an earlier post by me with subject "Iterator
implementation questions: copy constructor and postfix increment"),
I'm trying to implement an object that transfers ownership on
assignment or copy, just like auto_ptr does.

With an auto_ptr, I can do this:

#include <string>
#include <memory>
#include <iostream>

using namespace std;

auto_ptr<string> ap_factory() {
return auto_ptr<string>(new string("foo"));
}

int main() {
auto_ptr<string> s = ap_factory();
auto_ptr<string> s2 = s;
cout << "s1=" << s.get() << ", s2=" << *s2 << endl;
}

and when "s2 = s" executes, the pointer that was held by "s" is
trasferred to "s2", and then the pointer in "s" is set to NULL. So, I
see this output:

s1=0, s2=foo

When I try to implement the same behavior in my own class, I have
compilation problems. Here's my small test case:

#include <string>
#include <iostream>
using namespace std;

class C {
public:

C(string *_s) : s(_s) { }

C(C &that) : s(that.s) { }

C& operator=(C &that) {
if (&that != this) {
s = that.s;
}
return *this;
}

const string *get() {
return s.get();
}

private:
auto_ptr<string> s;
};

C c_factory() {
return C(new string("foo"));
}

int main() {
C myC = c_factory();
C myC2 = myC;
cout << "myC=" << myC.get() << ", myC2 = " << *(myC2.get()) << endl;

return 0;
}

When I try to compile this, I get:

test8.C: In function C c_factory():
test8.C:28: error: no matching function for call to C::C(C)
test8.C:10: note: candidates are: C::C(C&)
test8.C:8: note: C::C(std::string*)
test8.C: In function int main():
test8.C:32: error: no matching function for call to C::C(C)
test8.C:10: note: candidates are: C::C(C&)
test8.C:8: note: C::C(std::string*)

The problem seems to be that class C doesn't have a copy constructor
for "const C&" but only "C&", which is appropriate since the copied
object will be destroyed. I can verify this is the problem by
creating a constructor for "const C&", and making s mutable so I can
change it without the compiler complaining:

#include <string>
#include <iostream>
using namespace std;

class C {
public:

C(string *_s) : s(_s) { }

C(C const &that) : s(that.s) { }

C& operator=(C &that) {
if (&that != this) {
s = that.s;
}
return *this;
}

const string *get() {
return s.get();
}

private:
mutable auto_ptr<string> s;
};

C c_factory() {
return C(new string("foo"));
}

int main() {
C myC = c_factory();
C myC2 = myC;
cout << "myC=" << myC.get() << ", myC2 = " << *(myC2.get()) << endl;

return 0;
}

compiles and gives the expected output:

myC=0, myC2 = foo

Clearly, though, using "mutable" in this way is a hack.

Is there a better solution to this problem? auto_ptr seems not to have
a copy constructor that requires a const object, so there must be a
way, but I've looked at the source code for g++'s auto_ptr and can't
figure out what magic is making this work.

Thanks for any advice,

----ScottG.
 
V

Victor Bazarov

Scott said:
As a possible solution to a problem I'm trying to solve with an
iterator (see an earlier post by me with subject "Iterator
implementation questions: copy constructor and postfix increment"),
I'm trying to implement an object that transfers ownership on
assignment or copy, just like auto_ptr does.

With an auto_ptr, I can do this:

#include <string>
#include <memory>
#include <iostream>

using namespace std;

auto_ptr<string> ap_factory() {
return auto_ptr<string>(new string("foo"));
}

int main() {
auto_ptr<string> s = ap_factory();
auto_ptr<string> s2 = s;
cout << "s1=" << s.get() << ", s2=" << *s2 << endl;
}

and when "s2 = s" executes, the pointer that was held by "s" is
trasferred to "s2", and then the pointer in "s" is set to NULL. So, I
see this output:

s1=0, s2=foo

When I try to implement the same behavior in my own class, I have
compilation problems. Here's my small test case:

#include <string>
#include <iostream>
using namespace std;

class C {
public:

C(string *_s) : s(_s) { }

C(C &that) : s(that.s) { }

C& operator=(C &that) {
if (&that != this) {
s = that.s;
}
return *this;
}

const string *get() {
return s.get();
}

private:
auto_ptr<string> s;
};

C c_factory() {
return C(new string("foo"));
}

int main() {
C myC = c_factory();

Here you have a temporary from which you're trying to construct
an object. That operation requires binding a non-const reference
to the temporary, which is prohibited.

Take a look at how 'auto_ptr' is implemented and follow that instead
of trying to wrap it up.
C myC2 = myC;
cout << "myC=" << myC.get() << ", myC2 = " << *(myC2.get()) <<
endl;

return 0;
}

[..]

V
 
S

Scott Gifford

Victor Bazarov said:
Scott Gifford wrote:
[...]
C c_factory() {
return C(new string("foo"));
}
[...]

Here you have a temporary from which you're trying to construct
an object. That operation requires binding a non-const reference
to the temporary, which is prohibited.

I assume you mean the above snippet? I can fix this particular error
by replacing using a temporary:

C c_factory() {
C tmp(new string("foo"));
return tmp;
}

but one error still remains:

test8.C: In function int main():
test8.C:33: error: no matching function for call to C::C(C)
test8.C:10: note: candidates are: C::C(C&)
test8.C:8: note: C::C(std::string*)

Take a look at how 'auto_ptr' is implemented and follow that instead
of trying to wrap it up.

The actual code contains more member variables and methods, so it's
not quite as simple as what I posted. Since the auto_ptr code is
nontrivial and I don't understand all of it, I suspect it would be too
error-prone to try and copy its implementation into my class. I will
probably contine using the "mutable" hack if there's not a
straightforward way to make this work.

Thanks again, Victor!

----Scott.
 
V

Victor Bazarov

Scott said:
Victor Bazarov said:
Scott Gifford wrote:
[...]
C c_factory() {
return C(new string("foo"));
}
[...]

Here you have a temporary from which you're trying to construct
an object. That operation requires binding a non-const reference
to the temporary, which is prohibited.

I assume you mean the above snippet? I can fix this particular error
by replacing using a temporary:

C c_factory() {
C tmp(new string("foo"));
return tmp;
}

The variable you call 'tmp' has nothing to do with the "temporary" that
I refered to. A temporary object and an object with automatic storage
duration are not the same. Your 'tmp' here is the latter. The former
is created as the function returns a value.
but one error still remains:

test8.C: In function int main():
test8.C:33: error: no matching function for call to C::C(C)
test8.C:10: note: candidates are: C::C(C&)
test8.C:8: note: C::C(std::string*)



The actual code contains more member variables and methods, so it's
not quite as simple as what I posted. Since the auto_ptr code is
nontrivial and I don't understand all of it, I suspect it would be too
error-prone to try and copy its implementation into my class. I will
probably contine using the "mutable" hack if there's not a
straightforward way to make this work.

In case you don't read my other reply, think reference counting instead
of transfer of ownership.

V
 
B

Barry

Victor said:
Scott said:
Victor Bazarov said:
Scott Gifford wrote: [...]

C c_factory() {
return C(new string("foo"));
} [...]

Here you have a temporary from which you're trying to construct
an object. That operation requires binding a non-const reference
to the temporary, which is prohibited.
I assume you mean the above snippet? I can fix this particular error
by replacing using a temporary:

C c_factory() {
C tmp(new string("foo"));
return tmp;
}

The variable you call 'tmp' has nothing to do with the "temporary" that
I refered to. A temporary object and an object with automatic storage
duration are not the same. Your 'tmp' here is the latter. The former
is created as the function returns a value.

Victor,
adding this information may help understanding the problem.

initial value of reference to non-const must be an lvalue
(the compiler assume that you will somewhere modify the original data)

the temporary returned by the function is an rvalue.
 
D

dev_15

The original code posted at the top of the thread, compiles on the
MSVC 8 compiler!!
without errors

Does that compiler then allow binding of temporary to non const??
 
B

Barry

dev_15 said:
The original code posted at the top of the thread, compiles on the
MSVC 8 compiler!!
without errors

Does that compiler then allow binding of temporary to non const??

The MSVC has a compile option /Za, which disables the extension. Maybe
that's for some backward compatibility, I guess.
Try it out.
 
J

James Kanze

As a possible solution to a problem I'm trying to solve with an
iterator (see an earlier post by me with subject "Iterator
implementation questions: copy constructor and postfix increment"),
I'm trying to implement an object that transfers ownership on
assignment or copy, just like auto_ptr does.
With an auto_ptr, I can do this:
#include <string>
#include <memory>
#include <iostream>
using namespace std;
auto_ptr<string> ap_factory() {
return auto_ptr<string>(new string("foo"));
}
int main() {
auto_ptr<string> s = ap_factory();
auto_ptr<string> s2 = s;
cout << "s1=" << s.get() << ", s2=" << *s2 << endl;
}
and when "s2 = s" executes, the pointer that was held by "s" is
trasferred to "s2", and then the pointer in "s" is set to NULL. So, I
see this output:
s1=0, s2=foo
When I try to implement the same behavior in my own class, I have
compilation problems.

std::auto_ptr is very, very tricky. In addition, it has
undergone several revisions, and different implementations (e.g.
g++, VC++, etc.) may reflect different versions of the standard.

The main reason for this is that one National Body threatened a
no vote on the standard if you tried to instantiate a standard
container with an auto_ptr and didn't get an error. (A copy
constructor or assignment operator which transfers ownership
does not meet the requirements of Copiable or Assignable.) The
classical solution is just to cast away const (or use mutable)
in the object; the auto_ptr solution has a number of side
effects. (For example, copy initialization isn't legal. And a
pointer won't implicitly convert to an auto_pointer, which may
or may not be good.)
Here's my small test case:
#include <string>
#include <iostream>
using namespace std;
class C {
public:

C(string *_s) : s(_s) { }
C(C &that) : s(that.s) { }
C& operator=(C &that) {
if (&that != this) {

Just a nit, but note that the test for self assignment should
not be necessary, and is probably a pessimization.
(std::auto_ptr should handle self assignment correctly.)
s = that.s;
}
return *this;
}
const string *get() {
return s.get();
}
private:
auto_ptr<string> s;
};
C c_factory() {
return C(new string("foo"));
}
int main() {
C myC = c_factory();
C myC2 = myC;
cout << "myC=" << myC.get() << ", myC2 = " << *(myC2.get()) << endl;
return 0;
}
When I try to compile this, I get:

test8.C: In function C c_factory():
test8.C:28: error: no matching function for call to C::C(C)
test8.C:10: note: candidates are: C::C(C&)
test8.C:8: note: C::C(std::string*)
test8.C: In function int main():
test8.C:32: error: no matching function for call to C::C(C)
test8.C:10: note: candidates are: C::C(C&)
test8.C:8: note: C::C(std::string*)
The problem seems to be that class C doesn't have a copy constructor
for "const C&" but only "C&", which is appropriate since the copied
object will be destroyed.
Exactly.

I can verify this is the problem by
creating a constructor for "const C&", and making s mutable so I can
change it without the compiler complaining:
#include <string>
#include <iostream>
using namespace std;
class C {
public:
C(string *_s) : s(_s) { }
C(C const &that) : s(that.s) { }
C& operator=(C &that) {
if (&that != this) {
s = that.s;
}
return *this;
}
const string *get() {
return s.get();
}
private:
mutable auto_ptr<string> s;
};
C c_factory() {
return C(new string("foo"));
}
int main() {
C myC = c_factory();
C myC2 = myC;
cout << "myC=" << myC.get() << ", myC2 = " << *(myC2.get()) << endl;
return 0;
}
compiles and gives the expected output:
myC=0, myC2 = foo
Clearly, though, using "mutable" in this way is a hack.

True, but it's doubtlessly the simplest way.
Is there a better solution to this problem? auto_ptr seems
not to have a copy constructor that requires a const object,
so there must be a way, but I've looked at the source code for
g++'s auto_ptr and can't figure out what magic is making this
work.

auto_ptr uses an intermediate object (auto_ptr_ref, or something
like that), with conversions to and from auto_ptr. This allows
things like:

std::auto_ptr<T> f() ;
std::auto_ptr<T> a( f() ) ;

since there is a constructor for auto_ptr which takes an
auto_ptr_ref, and the auto_ptr returned by f() will convert
implicitly to an auto_ptr_ref. Note, however, that:

std::auto_ptr<T> a = f() ;

won't compile, because the semantics here are basically the same
as if you'd written:

std::auto_ptr<T> a = std::auto_ptr<T>( f() ) ;

except that an explicit constructor won't be considered in the
right hand side. Similarly, you can't copy the auto_ptr from a
const reference (e.g. like the parameter of push_back).

The need for "move" semantics (a copy where the source will
never be used again) are more general, and the language is being
modified to support them. Once that happens, things like this
should become much simpler (I think---I'm not really up to date
in this). Until then, I'd just go with the abuse of mutable,
and carefully document that the copy semantics are NOT true
copy, and that the type doesn't meet the Copiable constraints of
the standard containers.
 
C

Chris Fairles

The need for "move" semantics (a copy where the source will
never be used again) are more general, and the language is being
modified to support them.

In c++0x, there will be a smart pointer called std::unique_ptr. It is
not Assignable or Copyable but is Moveable.

i.e.

std::unique_ptr<int> p1(new int);
std::unique_ptr<int> p2;

std::unique_ptr<int> p3(p1); //illegal
p2 = p1; //illegal

p2 = std::move(p1); // legal, p1 transfers ownership to p2
std::unique_ptr<int> p4(std::move(p2)); // legal, p2 transfers
ownership to p4

Chris

P.S. it will also support custom deleters objects. a "deleter" is a
functor with operator()(T* t). std::default_delete basically does
operator(T* t){delete t;} (or delete [] t if T is of type T[] or T[N]
for some int N).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,066
Latest member
VytoKetoReviews

Latest Threads

Top