【问题标题】:Preventing copy construction and assignment of a return value reference防止复制构造和分配返回值引用
【发布时间】:2011-03-07 13:55:50
【问题描述】:

如果我有一个函数返回对我无法控制其源的类实例的引用,例如list<int>

list<int>& f();

我想确保它的值分配给另一个引用,例如:

list<int> &a_list = f();

如果用户改为这样做:

list<int> a_list = f(); // note: no '&', so the list is copied

我希望它是一个编译时错误,因为用户只会操作列表的副本而不是原始列表(这绝不是我的应用程序想要/想要的)。

有什么方法可以防止上面的复制构造和赋值(比如通过某种“包装器”类)?

理想情况下,如果要使用一些包装类,比如wrapper&lt;T&gt;,我希望它适用于任何类型的对象T


是的,我知道对于我确实可以控制的类,我可以简单地将复制构造函数和赋值运算符private 设为:

class MyClass {
public:
    // ...
private:
    MyClass( MyClass const& );
    MyClass operator=( MyClass const& );
};

禁止复制构造和赋值;但是,如上所示,我想为std::list 执行此操作,我不能简单地为其创建复制构造函数和赋值运算符private

【问题讨论】:

  • @GMan:当然这是微不足道的,因为它是 1 个字符的错误;但是,很容易忘记键入“&”。如果您仍然不明白这一点,那么只需忽略问题并继续前进。
  • 介意我问你为什么要这样做吗?
  • @Paul:但这就像说您需要一种方法来防止在打算使用x += 1 时执行x + 1。在“让我试着拯救你”而不是“你需要了解参考资料”的道路上,这是一个很大的痛苦。
  • @fingerprint211b:防止用户做他/她不应该做的事情,因为这是错误的事情(正如我最初所说的那样)。如果用户正在对列表的副本进行操作,那么显然他/她只对超出范围时将被销毁的副本进行更改。
  • @GMan:我不同意你的建议。这不是关于“学习如何编程”。例如,创建 auto_ptr 是为了帮助用户防止用户意外做错事。您可以在没有 auto_ptr 的情况下在 C++ 中完美编程,但您必须更加小心。我对您的 cmets 最大的问题是,您认为仅仅因为有人忘记输入“&”,他们就“不知道如何编程”。那是居高临下的,让你听起来很傲慢。仅供参考。

标签: c++ copy-constructor assignment-operator


【解决方案1】:

这是与我之前的答案不同的答案,因为问题已得到澄清。我之前的回答可能对有理智要求的人有用,所以我保持原样。

所需的行为是不可能的:请求能够一般返回看起来与T&amp; 完全相同但行为不像T 的东西。必须以某种方式让用户(和编译器!)知道这个返回的东西实际上不是引用这一事实。

【讨论】:

    【解决方案2】:

    嗯,你可以用包装器来做。为您的列表制作一个重载的包装器 -> 但永远不会为您提供对真实参考的访问。我想可能有办法解决这种方法,但它必须是故意的。应该足以告知客户他们真的不应该这样做。

    【讨论】:

    • 如果用户必须使用 operator->,那么他/她就不能像我上面显示的那样简单地写出正确的形式。
    • @Paul:在他们可以(并且被允许)做list&lt;int&gt;&amp; 的任何地方,他们都可以删除该引用。您必须更改语法或保留原来的“问题”。
    • “正确的形式”? WTF 这应该是什么意思?如果你坚持一个不可能的答案,那么我担心你会被困在向众神乞求改变宇宙的过程中。我试图为您提供一些可以解决您要解决的问题的东西。如果这还不够好……那么,祝你好运。
    • “正确形式”意味着可以写成“正常”:list &a_list = f();.
    【解决方案3】:

    你可以从这个类继承,并创建一个匹配的构造函数来调用父类的构造函数(例如,有相同的数据构造函数,只是将数据传递给父类),并使复制构造函数和私人复制作业。或者,您可以同时从 boost::noncopyable 和该类派生。然后,您可以安全地使用指向基类的指针/引用。

    编辑:如果该类有一个接口,您可以创建一个实现该接口的装饰器,该接口不可复制,并且不提供一种方法来获取它所包装的对象的引用/指针。

    如果这些都不是一个选项,您可以编写一个具有相同确切方法并且看起来相同的类,而无需复制构造函数和复制赋值,它将调用您正在保护的类的适当方法(再次,装饰者,艰难的方式)。但我会避免这种情况。

    【讨论】:

    • 根据我上面添加的文本,不可能从某些任意类 T 继承并具有匹配的构造函数。
    • 使用此方法可以做到的最好的绝对是使用默认构造函数捕获所有内容。不过,鉴于您的其他要求,这可能是您的最佳选择。
    • @pkh:我不明白您所说的“使用默认构造函数捕获所有内容”是什么意思。也许你可以写一些代码(?)。
    • 我的意思是这个答案中描述的解决方案适用于任何具有 0 参数构造函数的类。 “默认”是一个错误的词选择。
    【解决方案4】:

    我不相信语言中有任何东西可以让你这样做。可以说,您可以返回一个指针,以便他们必须采取明确的行动来复制它。

    OTOH,这是您可以使用的包装器示例。请注意,Uncopyable 按值而不是引用返回。 (不过没关系,因为它可能只是指针大小的。)

    #include <iostream>
    #include <list>
    
    template <typename T>
    class Uncopyable
    {
    public:
        Uncopyable(T& r) : ref(r) {}
    
        T* operator->() { return &ref; }
    
    private:
        T& ref;
    };
    
    Uncopyable<std::list<int> > get_list()
    {
        static std::list<int> l;
        l.push_back(l.size());
        return l;
    }
    
    int main() 
    {
        for (int i = 0; i < 10; ++i)
        {
            Uncopyable<std::list<int> > my_l = get_list();
            std::cout << my_l->size() << std::endl;
        }
    }
    

    【讨论】:

    • 问题在于用户必须了解 Uncopyable 并在他们的代码中使用它。理想情况下,我希望解决方案是透明的。
    • 这本身就是一个答案。我只是想确保真的没有聪明的方法可以做我想做的事。
    • 在我的帖子顶部查看我的 cmets;如果您非常想要这种行为,请转到指针。您所要求的是将引用视为不是它所引用的事物,这正是它的存在点。
    【解决方案5】:

    我觉得这是一个奇怪的要求。复制列表或将其用作参考的策略通常应由用户决定,但如果由于某种原因,复制列表永远不正确,那么包装类就可以解决问题。

    如果用户知道他在做什么,他应该了解使用列表的深层副本与使用浅层副本和修改原始副本之间的区别。如果用户不明白这一点,他还没有使用 C++ 的业务。

    [编辑] 我刚刚注意到之前有人发布了几乎相同的解决方案。 这个类可以很简单:

    template <class T>
    class NoncopyablePtr
    {
    private:
        T* ptr;
    
    public:
        /*implicit*/ NoncopyablePtr(T* iptr): ptr(iptr) {}
    
        T* operator->() const
        {
            return ptr;
        }
    };
    

    这将使用户很难复制指针。他们必须显式调用 operator-> 并取消引用结果。现在只需返回 NoncopyablePtr> ,您将让客户端(尽管并非不可能)制作该列表的副本变得非常困难。

    如果你不喜欢使用 operator-> 那么恐怕真的没有其他方法可以阻止用户轻松复制结果。

    【讨论】:

      猜你喜欢
      • 2011-01-28
      • 2012-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多