文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>常见错误49: 未能意识到c++语言中复制操作的固守行为----读书笔记《c++ gotchas...

常见错误49: 未能意识到c++语言中复制操作的固守行为----读书笔记《c++ gotchas...

时间:2010-08-15  来源:lzueclipse

C++语言对于其复制操作极其审慎,尤其对于class对象而言。如果你不自己写,编译器就会代劳。

注意,复制赋值操作和复制构造函数(以及其他任何的构造函数,还有所有的析构函数)都不从基类继承。

所有的class都得定义自己的复制操作。

复制构造函数的默认实现,就是执行一个按成员进行的初始化(member-by-member initialization)。

考虑下面这个简单class实现:

template

class NBString {

public:

    explicit NBString(const char *name);

    //…

private:

    std::string name_;

    size_t len_;

    char s_[maxlen];

};

假定没有定义任何复制操作,那么编译器就会暗中替我们合成。它们具有public访问层级,而且是inline的。

NBString<32> a(“String 1”);

//…

NBString<32> b(a);

编译器合成的复制构造函数会执行一个按成员进行的初始化操作:

以a.name_初始化b.name_;

a.len初始化b.len_;

a.s_的各个元素来初始化b.s_的各个元素。

对于 b = a;

隐式合成的复制操作会执行一个按成员进行的赋值操作:

b.name_赋值为a.name_;

b.len_赋值为a.len_;

b.s_的各个元素赋值为a.s_中的对应元素。

以上隐式合成的复制操作将实现正确的复制语义。

再看一个例子,一个普通类,不是模板类:

class NBString {

public:

    explicit NBString(const char *name, int maxlen = 32)

    : name_(name), len_(0), maxlen_(maxlen),

      s_(new char[maxlen]) {

        s_[0] = ‘\0’;

    }

    ~NBString() {

        delete [] s_;

    }

private:

    std::string name_;

    size_t len_;

    size_t maxlen_;

    char *s_;

};

//…

NBString c(“String 2”);

NBString d(c);

NBString e(“String 3”);

e = c;

隐式构造函数把c和d的s_成员设置成同一个地址空间,当d和c的析构函数一次delete [] s_时,

有“二次清除“的问题。 相似地,当e被赋值为c的时候,e的s_成员被设置成合c的s_成员一样的存储块,

对c的清除操作也会让e的s_成员陷入空悬(dangling)。

所以NBString正确的实现必须既要写复制构造函数,又要写复制赋值运算符,不能让编译器来做:

class NBString {

public:

    //…

    NBString(const NBString &);

    NBString & operator = (Const NBString &);

private:

    std::String name_;

    size_t len_;

    size_t maxlen_;

    char *s_;

};

//…

NBString::NBString(const NBString &that)

    : name_(that.name_), len_(that.len_), maxlen_(that.maxlen_),

    s_(strcpy(new char[that.maxlen_], that.s_))

{

}

NBString &NBString::operator = (const NBString &rhs) {

    if(this != rhs) {

       name_ = rhs.name_;

       len_ = rhs.len_;

      maxlen_ = rhs.maxlen_;

      char *temp = new char[rhs.maxlen_];

      delete [] s_;

      s_ = strcpy(temp, rhs.s_);

   }

    return *this;

}

对于复制操作有3种选择:

1)工程师显式撰写

2)由编译器隐式合成

3)工程师显式拒绝

举个禁止复制的例子:

class NBString {

public:

    //…

private:

    NBString(const NBString &);

    NBString & operator = (const NBString &);

    //...

};

仅仅声明private访问层级的复制操作,但不给出定义的做法可以禁止复制操作。

大多数的代码不具有对该class的复制操作的访问层级,任何在该class的成员函数以及友元中意外调用复制的操作,

都会在链接时出错。

下面这个例子列举了三种企图与编译器斗智斗勇的代码,确是徒劳无益的:

class Derived;

class Base {

public:

    Derived &operator = (const Derived &);

    virtual Base &operator = (const Base &);

};

class Derived : public Base {

public:

    using Base::operator = ;//被遮掩(hidden)

    template <class T>

    Derived &operator = (const T&);//并非复制赋值操作

    Derived &operator = (const Base &);//并非复制赋值操作

};

1)复制操作无继承语义,即使是using声明从非虚基类导入的复制运算符也不能阻止编译器生成一个,并把这个编译器生成的

复制运算符遮掩导入的实现。

 在基类类型中显式地写出了派生类的名字,这是很糟糕的实现。

2)使用模板赋值成员函数也没用,:模板成员从不用于实现复制操作。

3)基类里的复制赋值运算符可以再派生类中以虚函数方式写,但改写后的那个复制运算符不再是一个复制赋值运算符。

只能说是一个以基类对象为参数的赋值运算符。

总结:要么自己撰写复制操作,要么编译器替你合成一个。

相关阅读 更多 +
排行榜 更多 +
辰域智控app

辰域智控app

系统工具 下载
网医联盟app

网医联盟app

运动健身 下载
汇丰汇选App

汇丰汇选App

金融理财 下载