【问题标题】:Can I write different copyCtor for const and non-const instances?我可以为 const 和非常量实例编写不同的 copyCtor 吗?
【发布时间】:2012-11-12 12:29:15
【问题描述】:

我有以下问题:

我有一个班级应该这样做:

Obj o;
Obj o1(o), o1=o; // deep-copies
const Obj c(o), c=o; // deep-copies
const Obj c1(c), c1=c; // shallow-copies
Obj o2(c), o2=c; // deep-copies

如果没有继承,我怎样才能做到这一点? (我的意思是我会做 Const_obj 继承自 Obj 否则。)

编辑:

直接使用o.clone() 不是一种选择,因为这样我很容易通过意外克隆引入错误。

编辑:

最后,使用Scott Meyers 的Effective C++ 的想法,有一个适当的、完整的懒惰评估解决方案。在下面查看我的答案。

【问题讨论】:

  • 我认为您可能需要添加一个 clone() 方法并明确执行您想要的操作。
  • 这就是我想要避免的,不要在我的代码中写入难以跟踪的错误。那样很容易。
  • 嗯,我认为默默地做这件事要危险得多——尤其是在你换工作和新人进来之后......(我的 2 penneth)
  • 您可以使复制构造函数和赋值运算符受保护,因此您将不得不使用“深拷贝”或“浅拷贝”方法
  • @Caribou:它只是一个普通的从外部深度复制的类,但在引擎盖下加速了。仅提供实现细节。

标签: c++ copy-constructor deep-copy shallow-copy


【解决方案1】:

不,你不能。

  • 构造函数不能是 cv 限定的,因此不能强制它构造一个 const 对象。
  • 函数的返回类型(包括运算符)不是其签名的一部分,因此您不能通过更改函数的返回类型来重载函数。

另外,如果可能的话,我会发现它真的令人困惑。只需制作适合您需要的方法,并以明确的方式命名即可。

【讨论】:

  • 这就是我的想法。 :(
  • 终于找到了正确的实现方式。在下面检查我的答案。
【解决方案2】:

看完Scott Meyers的Effective C++,以下是解决方法:

定义一个执行惰性评估的模板(使用引用计数):

class Obj : private lazy<Obj_data>{};

惰性存储 Obj_data 私有,具有受保护的访问器,一个用于修改,一个用于只读访问。
如果需要,修饰符访问器首先深度复制Obj_data,然后移交对数据的引用。只读访问器只返回一个 const 引用。

这样做的总成本是存储 2 个额外的指针(一个用于数据,一个用于计数器)和一个计数器。

实现是这样的:

class lazy{
protected:
  lazy(const lazy&obj){lazy_copy(obj);}
  //(the required constructors, operator= ...)

  // accessors:
  const Obj_data& data() const {return *od;}
  Obj_data& mod_data() {make_private(); return *od;}
private:
  void lazy_copy(const lazy& obj);
  void make_private(); // this does the actual deep-copy, as late as possible.
private:
  counter*;
  Obj_data* od;
};

所以,读取和修改Obj 的属性就可以了

void Obj::method(){
   cout << data().some_attribute;    // simple read
   mod_data().i = 10;                // simple modify
   const Obj_data& const_d = data(); // assignable for lots of read-outs
   Obj_data& var_d = mod_data();     // assignable for lots of modifications.
}

请注意,您只能在 const 成员中使用 data(),因为 mod_data() 是类中的非常量函数,因此此解决方案是完全安全的,开销很小。

理论背景:问题中期望的行为是一个实现细节,与客户无关。因此我们通过私有继承来解决它。

【讨论】:

    【解决方案3】:

    您可以部分地使用虚拟参数:

    class C {
    public:
        struct NonStandardCopy { };
    
        C (const C &) {
            // "ordinary" copy constructor with default behavior
        }
    
        C (const C &, NonStandardCopy) {
            // "other" "copy" constructor
        }
    };
    
    C c = c1; // default
    C c (c1); // default
    C c (c1, C::NonStandardCopy ()); // non-default
    

    编辑:仅克隆方法可能是您想要的(与移动语义一起,性能影响可能不会太大):

    class C {
    private:
        struct DeepCopy { };
        struct ShallowCopy { };
    
        C (const C &) = delete;
    
        C (const C &, DeepCopy) {
            // deep copy
        }
    
        C (const C &, ShallowCopy) {
            // shallow copy
        }
    public:
        // move constructor
        C (C && other) = default;
    
        const C clone () const { // 1
            // shallow copy
            return C (*this, ShallowCopy ());
        }
    
        C cloneToNonConst () const {  // 2
            // deep copy
            return C (*this, DeepCopy ());
        }
    
        C clone () { // 3
            return cloneToNonConst ();
        }
    };
    
    C o;
    C o1 = o.clone (); // call 3
    const C o2 = o1.clone (); // call 3
    const C o3 = o2.clone (); // call 1
    C c4 = o3.cloneToNonConst (); // call 2; o3.clone () will give error
    

    【讨论】:

    • +1,好主意,我现在正在寻找使其自动化,一旦我准备好使用Const_obj/Obj,我会回到这里并写。
    • 很高兴我的回答很有用。我刚刚明白你想区分构造一个常量和构造一个非常量。我认为这在构造函数中是不可能的。但是,您可以使用公共工厂/克隆函数(const/non-const 返回,const 与 non-const 成员函数)并将构造函数声明为私有。
    • 抱歉,由于 copyCtor 是私有的,所以当我将测试放入主函数时,它还没有为我编译。
    • 啊,你可能还需要实现一个公共移动构造函数:public: C (C &amp;&amp; other) { // move field-by-field },或者,如果你的编译器支持,public: C (C &amp;&amp; other) = default;
    • 感谢您的建议!我基于它的实现在一个答案中。
    猜你喜欢
    • 1970-01-01
    • 2011-01-31
    • 2020-11-30
    • 2021-08-27
    • 1970-01-01
    • 2018-09-27
    • 1970-01-01
    • 2022-01-06
    • 2018-03-28
    相关资源
    最近更新 更多