我能为const和非const实例编写不同的copyCtor吗?

7

我有以下问题:

我有一个类应该完成这个功能:

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()方法并明确地执行你想要的操作。 - Caribou
这就是我想要避免的,不想在我的代码中写入难以追踪的错误。那样太容易了。 - Barney Szabolcs
我认为默默地这样做要危险得多 - 特别是当你换工作后,有新人进来...(我的看法) - Caribou
1
你可以将复制构造函数和赋值运算符设置为受保护的,这样你就必须使用“深拷贝”或“浅拷贝”方法。 - Kamil Klimek
@Caribou:从外部来看,它只是一个普通的深度复制类,但在内部加速了。这只是一个实现细节。 - Barney Szabolcs
3个回答

4
不行,你不能这样做。
构造函数不能被cv限定,所以你不能强制它构造一个const对象。
函数(包括操作符)的返回类型不是其签名的一部分,因此你不能仅通过改变其返回类型来重载一个函数。
而且,如果可能的话,我会觉得非常困惑。只需编写适合您需要的方法,并用一种明确的方式命名它们即可。

那就是我所想到的。:( - Barney Szabolcs
最终找到了正确的实现方式。请查看下面我的回答。 - Barney Szabolcs

2
阅读完《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()是该类中的非const函数,因此此解决方案完全安全且开销小。
理论背景:问题中所需的行为是实现细节,与客户无关。因此,我们通过私有继承来解决它。

0

你可以使用一个虚拟参数来实现部分功能:

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准备好了,我会回来写的。 - Barney Szabolcs
很高兴我的回答有用。我刚刚明白你想区分构造常量和非常量。我认为这在构造函数内部是不可能的。但是,您可以使用公共工厂/克隆函数(返回const / non-const,const vs. non-const成员函数)并将构造函数声明为私有。 - JohnB
抱歉,由于copyCtor是私有的,当我将测试放入主函数中时,它仍无法编译通过。 - Barney Szabolcs
啊,你可能还需要实现一个公共的移动构造函数:public: C (C && other) { // move field-by-field } 或者,如果你的编译器支持的话,public: C (C && other) = default; - JohnB
谢谢你的想法!我基于它的实现已经在一个答案中了。 - Barney Szabolcs
啊,终于找到了一个合适的实现方式,看看我的新答案吧,我投票删除了旧答案。 - Barney Szabolcs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接