哪个更好:一个谎言的复制构造函数或一个非标准的复制构造函数?

时间:2010-06-09 15:38:51

标签: c++

我有一个包含不可复制句柄的C ++类。但是,该类必须具有复制构造函数。所以,我实现了一个将句柄的所有权转移到新对象(如下所示),

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

我的问题是标准的复制构造函数采用const-reference,我正在修改该引用。所以,我想知道哪个更好(以及为什么):

  1. 非标准的复制构造函数: Foo( Foo& other );

  2. '谎言'的复制构造函数: Foo( const Foo& other );


  3. 编辑:

    DuplicateHandle()仅适用于特定类型的句柄。这不是其中的一个。无法复制,复制或克隆此句柄。

    有几个人指出我错误地认为它是一个非标准的复制构造函数,而std::auto_ptr就是这样做的。我认为这可能是要走的路。但是,每当我使用类时,当我使复制ctor取非const值时,我最终会收到警告。例如:

    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
    

    有人可以建议我应该对他们做些什么吗?


    EDIT2:

    每个人似乎都在引导我走向std::auto_ptr<>的做事方法。所以,我看了那里,它使用一个中间结构来解决我在第一次编辑中描述的问题。这是我提出的解决方案。

    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
    

    它在警告级别4上干净地编译并且似乎有效。请让我知道它是否比我原来的帖子更不负责任。

11 个答案:

答案 0 :(得分:6)

两者都很可怕。如果您的类具有不可复制的成员变量,那么您的类是不可复制的。

如果你的类是不可复制的是非常不可接受的,一个解决方法就是有一个指向“state”类/结构的共享指针来存储不可复制的对象(它本身是不可复制的),但你的类可以复制共享指针通过标准的复制构造函数。

答案 1 :(得分:4)

第一个选项以auto_ptr

的形式有一个完善的先例

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

auto_ptr放弃指针并在复制时重置。

确保函数不会更改作为const传递的参数的标准比copy-ctor标准强得多,后者实际上并不是非常正式。

此外,根据标准,通过抛弃其const来操纵const值是未定义的行为。如果您确定引用引用了非const对象,或者您将const对象传递给不会修改该值的(const-incorrect)函数,则可以执行此操作。

答案 2 :(得分:3)

你的课基本上做的是std :; auto_ptr。 auto_ptr的复制构造函数如下所示:

auto_ptr( auto_ptr & p );

这意味着它不能在某些情况下使用,例如在标准容器中。这可能是最好的做法,但值得注意的是auto_ptr已在C ++ 0x中弃用。

答案 3 :(得分:3)

也许复制构造函数应该只使用DuplicateHandle()来制作一个真正的副本。

答案 4 :(得分:3)

好吧,如果你真的需要一个拷贝构造函数,那么你必须确保它具有正确的语义。如果您实施任何一种方法,那么您就会遇到麻烦。依赖于您的复制构造函数的代码将不知道您的所有权转移方案,这可能会产生不可预测的结果。 (例如,这就是你不能在STL容器中使用std :: auto_ptr的原因。)

如果您确实不需要复制构造函数,则创建一个复制对象并传输句柄所有权的方法。

如果确实需要复制构造函数,则需要一种方法来共享副本之间的句柄。我建议使用辅助对象来存储句柄和引用计数,这样你就知道何时关闭句柄。我还会重新检查你的假设,即句柄是不可复制的。如果唯一的问题是你必须完全关闭它一次,那么你可以使用DuplicateHandle()函数解决问题。

最后一点说明:我会说编程中的基本规则是从不谎言函数的作用。

答案 5 :(得分:2)

也许,不应该为非可复制的对象使用非标准或奇怪的复制构造函数,而应考虑不复制它。您可以使用新的shared_ptr机制或unique_ptr进行所有权切换,而不是自己担心这些内容。

答案 6 :(得分:2)

使用非const复制构造函数是可以的。我不知道为什么你称之为“非标准”。该标准明确指出(§12.8/ 2)复制构造函数参数不必是const。它通常是,因为修改其他对象很少是必要的,并且有其缺点(例如,不能在标准容器中使用)。

但我宁愿重新审视设计:你真的需要复制一个明显不可复制的对象吗?语义说不然。或者,你不能以安全的方式复制底层句柄吗?

  • 要么只是分配它并使用共享所有权语义,也许与引用计数器一起使用,这样你就不会关闭句柄两次,或者说太早。
  • 或者导致句柄的底层对象重复。

答案 7 :(得分:1)

我肯定会实现你在这里的拷贝构造函数。 Detach方法声明为const但实际上并非如此。最好使复制构造函数遵循标准形式,但将其设为私有和/或使其抛出调用。然后,您可以提供克隆方法,该方法复制句柄和任何其他适用的属性。

答案 8 :(得分:1)

这很容易:非标准的。修改其const参数的构造函数很难由开发人员预测。您需要将其记录下来,并且开发人员需要知道查看文档以获得非常常见的用途......这是最不可能的。

非标准构造函数虽然引起了人们的注意。

此外,这是标准如何使用auto_ptr。为什么?所以你不能不小心把它放在一个容器里。使用非标准版本的另一个好理由。

答案 9 :(得分:0)

关于你的编辑问题:这不是你传递的非const对象,而是你试图复制非const 临时,这是不允许的通过非const引用传递。你可以声明一个实际的对象,并且应该纠正它。

Fuzz bar() const { Fuzz fuzz; return fuzz; };

答案 10 :(得分:0)

最简单的解决方案是将HANDLE h替换为boost::shared_ptr<HANDLE> ph。现在添加一个私人便利函数HANDLE h() { return *ph; }。因此,默认复制ctor将做正确的事情。

当然,如果两个对象同时尝试使用句柄,则可能会遇到一些问题。但是,由于您的原始设计在没有手柄的情况下离开了原始对象,因此无论如何情况似乎并非如此。