RVO/NRVO and public undefined copy constructor
I'm fighting the following proposal now, and I want to know legal and for lesser extent moral arguments against it or for it.
What we had:
#include <vector>
class T;
cla开发者_如何学Pythonss C
{
public:
C() { }
~C( ) { /*something non-trivial: say, calls delete for all elements in v*/ }
// a lot of member functions that modify C
// a lot of member functions that don't modify C
private:
C(C const &);
C& operator=(C const&);
private:
std::vector< T* > v;
};
void init(C& c) { } // cannot be moved inside C
// ...
int main()
{
// bad: two-phase initialization exposed to the clients
C c;
init(c);
// bad: here follows a lot of code that only wants read-only access to c
// but c cannot be declared const
}
What has been proposed:
#include <vector>
class T;
class C
{
public:
C() { }
~C( ) { /*calls delete for all elements in v*/ }
// MADE PUBLIC
C(C const &); // <-- NOT DEFINED
// a lot of member functions that modify C
// a lot of member functions that don't modify C
private:
C& operator=(C const&);
private:
vector< T* > v;
};
C init() // for whatever reason object CANNOT be allocated in free memory
{
C c;
// init c
return c;
}
// ...
int main()
{
C const & c = init();
}
This compiles and links (and works) using recent g++ (which is the only target compiler) both 4.1.2 and 4.4.5 -- because of (N)RVO the copy-constructor is never called; destructor is called at the end of main() only.
It is claimed that the technique is perfectly fine, because there is no way copy-constructor could be mis-used (if it ever have been generated it would be linker error), and making it public prevents compiler for complaining about private one.
It looks really-really wrong for me to use such trick, which I feel contradicts the C++ spirit and looks more like hack -- in the bad sense of the word.
My feelings is not sufficient argumentation, so I'm looking for technicalities now.
Please don't post textbook C++ stuff here:
- I'm aware of "The Rule of Three" and have read through 12.8/15 and 12.2 of Holy Standard;
- I can use neither
vector<shared_ptr<T> >
norptr_vector<T>
; - I cannot allocate
C
in free memory and return it frominit
viaC*
.
Thank you.
This compiles and links (and works) using recent g++ (which is the only target compiler) both 4.1.2 and 4.4.5 -- because of (N)RVO the copy-constructor is never called; destructor is called at the end of main() only.
While it may work with GCC, your code really has undefined behavior because it references a function that's not defined. In such a case, your program is ill-formed; no diagnostic required. Which means that GCC may ignore the rule violation, but other compilers may diagnose it or do something else strange.
So on those grounds, I would reject this way.
My feelings is not sufficient argumentation, so I'm looking for technicalities now.
You want to have move semantics here. What about having this explicit?
class T;
class C;
struct CMover {
C *c;
private:
CMover(C *c):c(c) { }
friend CMover move(C &c);
};
class C {
public:
C() { }
~C( ) { /*calls delete for all elements in v*/ }
C(CMover cmove) {
swap(v, cmove.c->v);
}
inline operator CMover();
// a lot of member functions that modify C
// a lot of member functions that don't modify C
private:
C& operator=(C const&); // not copy assignable
C(C &); // not lvalue copy-constructible
private:
vector< T* > v;
};
CMover move(C &c) { return CMover(&c); }
C::operator CMover() { return move(*this); }
Now you can say
C init() // for whatever reason object CANNOT be allocated in free memory
{
C c;
return move(c);
}
int main() {
C const c(init());
}
The obvious answer is that the compiler is under no obligation to elide the copy construction, especially if optimization is disabled (although g++ still elides the copy even without optimization). Thus, you're restricting portability.
Most likely if it compiles on a particular compiler it would function as expected though.
I know there are a ton of seemingly arbitrary/artificial restrictions on what you can do, but are you able to use a proxy holder for C
?
class C
{
private:
C() { }
~C( ) { /*calls delete for all elements in v*/ }
// ...
friend class C_Proxy;
};
class C_Proxy
{
public:
C_Proxy() : c_() { init(c_); }
// Or create a public const& attribute to point to this.
const C& get_C() const { return c_; }
private:
C c_;
};
This looks like something you wont get out of peoples heads on technical reasons alone (aka "But it compiles and works on our compiler!"), so maybe a conceptually simpler approach might be a good idea?
If your concern is the constness ...
C c; init(c); // bad: here follows a lot of code that only wants read-only access to c // but c cannot be declared const
vs.
C const & c = init();
the simplest (as in: no hacks and proxy stuff required) solution might be:
C c_mutable_object;
init(c_mutable_object);
C const& c = c_mutable_object;
// ... lots of code with read-only access to c
精彩评论