Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

which is better: a lying copy constructor or a non-standard one?

Tags:

c++

I have a C++ class that contains a non-copyable handle. The class, however, must have a copy constructor. So, I've implemented one that transfers ownership of the handle to the new object (as below),

class Foo
{
public:
    Foo() : h_( INVALID_HANDLE_VALUE )
    {
    };

    // transfer the handle to the new instance
    Foo( const Foo& other ) : h_( other.Detach() )
    {
    };

    ~Foo()
    {
        if( INVALID_HANDLE_VALUE != h_ )
            CloseHandle( h_ );
    };

    // other interesting functions...

private:

    /// disallow assignment
    const Foo& operator=( const Foo& );

    HANDLE Detach() const
    {
        HANDLE h = h_;
        h_ = INVALID_HANDLE_VALUE;
        return h;
    };

    /// a non-copyable handle
    mutable HANDLE h_;
}; // class Foo

My problem is that the standard copy constructor takes a const-reference and I'm modifying that reference. So, I'd like to know which is better (and why):

  1. a non-standard copy constructor: Foo( Foo& other );

  2. a copy-constructor that 'lies': Foo( const Foo& other );


Edit:

DuplicateHandle() only works on specific types of handles. This is not one of them. This handle cannot be duplicated, copied, or cloned.

Several people have pointed out that I was mistaken in suggesting it is a non-standard copy constructor and that std::auto_ptr does this. I think that's probably the way to go. But, I end up with warnings every time I use the class when I make the copy ctor take a non-const value. For example:

namespace detail {
    class Foo { ... };
};

class Buzz
{
public:
    typedef detail::Foo Fuzz;

    Fuzz bar() const { return Fuzz(); }; // warning here
};

warning C4239: nonstandard extension used : 'argument' : conversion from 'Foo' to 'Foo &'
1> A non-const reference may only be bound to an lvalue; copy constructor takes a reference to non-const

Can anybody suggest what I should do about them?


Edit2:

Everybody seems to be steering me towards std::auto_ptr<>'s method of doing things. So, I looked there and it uses an intermediate structure to get around the issue I described in the first edit. This is the solution I came up with.

class Foo;

struct Foo_ref
{
    explicit Foo_ref( Foo& other ) : ref_( other ) {};
    Foo& ref_;
private:
    const Foo_ref& operator=( const Foo_ref& );
}; // struct Foo_ref

class Foo
{
public:
    Foo() : h_( INVALID_HANDLE_VALUE )
    {
    };

    // transfer the handle to the new instance
    Foo( Foo_ref other ) : h_( other.ref_.Detach() )
    {
    };

    ~Foo()
    {
        if( INVALID_HANDLE_VALUE != h_ )
            CloseHandle( h_ );
    };

    operator Foo_ref()
    {
        Foo_ref tmp( *this );
        return tmp;
    };

    // other interesting functions...

private:

    /// disallow assignment
    const Foo& operator=( const Foo& );

    HANDLE Detach()
    {
        HANDLE h = h_;
        h_ = INVALID_HANDLE_VALUE;
        return h;
    };

    /// a non-copyable handle
    HANDLE h_;
}; // class Foo

It compiles cleanly on warning level 4 and seems to work. Please, let me know if it is somehow more irresponsible than my original post.

like image 888
PaulH Avatar asked Nov 28 '22 04:11

PaulH


2 Answers

Both are horrible. If your class has a member variable which is noncopyable, then your class is noncopyable.

If having your class be noncopyable is really unacceptable, one work around is to have a shared pointer to a "state" class/struct to store noncopyable objects (which itself is noncopyable), but your classes can copy the shared pointer around via the standard copy constructor.

like image 88
Terry Mahaffey Avatar answered Dec 19 '22 06:12

Terry Mahaffey


The first option has a well established precedent in the form of auto_ptr:

http://www.cplusplus.com/reference/std/memory/auto_ptr/auto_ptr/

auto_ptr gives up its pointer and is reset when it is copied.

The standard that ensures that a function doesn't change arguments passed as const is much stronger than the copy-ctor standard which is really not very formal.

Also, manipulating a const value by casting away its constness is undefined behavior according to the standard. You can do it if either you are certain that the reference refers to a non-const object or you are passing a const object to a (const-incorrect) function that will not modify the value.

like image 40
shoosh Avatar answered Dec 19 '22 06:12

shoosh