首页 文章

三规则的例外情况?

提问于
浏览
16

我已经阅读了很多关于C Rule of Three的内容 . 很多人都发誓 . 但是当规则被陈述时,它几乎总是包括像"usually," "likely,"或"probably,"这样的词,表示存在例外 . 我没有't seen much discussion of what these exceptional cases might be -- cases where the Rule of Three does not hold, or at least where adhering to it doesn'提供任何优势 .

My question is whether my situation is a legitimate exception to the Rule of Three. 我相信在我下面描述的情况中,需要一个明确定义的复制构造函数和复制赋值运算符,但默认(隐式生成的)析构函数将正常工作 . 这是我的情况:

我有两个类,A和B.这里讨论的是A. B是A的朋友.A包含B对象 . B包含一个A指针,用于指向拥有B对象的A对象 . B使用此指针来操纵A对象的私有成员 . 除了A构造函数之外,B永远不会被实例化 . 像这样:

// A.h

#include "B.h"

class A
{
private:
    B b;
    int x;
public:
    friend class B;
    A( int i = 0 )
    : b( this ) {
        x = i;
    };
};

和...

// B.h

#ifndef B_H // preprocessor escape to avoid infinite #include loop
#define B_H

class A; // forward declaration

class B
{
private:
    A * ap;
    int y;
public:
    B( A * a_ptr = 0 ) {
        ap = a_ptr;
        y = 1;
    };
    void init( A * a_ptr ) {
        ap = a_ptr;
    };
    void f();
    // this method has to be defined below
    // because members of A can't be accessed here
};

#include "A.h"

void B::f() {
    ap->x += y;
    y++;
}

#endif

我为什么要这样设置我的课程?我保证,我有充分的理由 . 这些类实际上比我在这里包含的更多 .

所以剩下的很容易,对吗?没有资源管理,没有三巨头,没问题 . 错误! A的默认(隐式)复制构造函数是不够的 . 如果我们这样做:

A a1;
A a2(a1);

我们得到一个与 a1 相同的新A对象 a2 ,这意味着 a2.ba1.b 相同,这意味着 a2.b.ap 仍然指向 a1 !这不是我们想要的 . 我们必须为A复制构造函数,复制默认复制构造函数的功能,然后将新 A::b.ap 设置为指向新的A对象 . 我们将此代码添加到 class A

public:
    A( const A & other )
    {
        // first we duplicate the functionality of a default copy constructor
        x = other.x;
        b = other.b;
        // b.y has been copied over correctly
        // b.ap has been copied over and therefore points to 'other'
        b.init( this ); // this extra step is necessary
    };

出于同样的原因,复制赋值运算符是必需的,并且将使用复制默认复制赋值运算符的功能,然后调用 b.init( this ); 的相同过程来实现 .

但是没有必要使用明确的析构函数;这种情况是三规则的例外 . 我对吗?

3 回答

  • 4

    唐't worry so much about the 1712099 . Rules aren'盲目地服从;他们已经想到了 . 而你've concluded that the destructor wouldn' t这样做 . 所以不要忘记写析构函数,泄漏资源 .

    同样,这种设计为B :: ap创造了错误的可能性 . 这是一整类潜在的错误,如果它们是单个类,或者以更强大的方式捆绑在一起,可以消除 .

  • 9

    似乎 BA 强烈耦合,并且总是应该使用包含它的 A 实例? A 总是包含一个 B 实例?他们通过友谊访问彼此的私人成员 .

    因此,人们想知道为什么他们是完全独立的课程 .

    但是假设由于某些其他原因需要两个类,这里有一个简单的解决方法,可以摆脱所有构造函数/析构函数的混淆:

    class A;
    class B
    {
         A* findMyA(); // replaces B::ap
    };
    
    class A : /* private */ B
    {
        friend class B;
    };
    
    A* B::findMyA() { return static_cast<A*>(this); }
    

    您仍然可以使用包含,并使用 offsetof 宏从 Bthis 指针中找到 A 的实例 . 但这比使用 static_cast 并将编译器登记到指针数学更麻烦 .

  • 2

    我和@dspeyer一起去 . 你想,你决定 . 实际上有人已经得出结论,三个规则通常(如果你在设计中做出正确的选择)归结为两个规则:使你的资源由图书馆对象(如上面提到的智能指针)管理,你通常可以摆脱析构函数 . 如果你足够幸运,你可以摆脱所有,并依靠编译器为你生成代码 .

    旁注:您的复制构造函数不会复制编译器生成的复制构造函数 . 您在其中使用复制赋值,而编译器将使用复制构造函数 . 摆脱构造函数体中的赋值并使用初始化列表 . 它会更快更清洁 .

    不错的问题,很好的答案形式本(另一个混淆我的同事在工作中的技巧),我很高兴给你们两个赞成票 .

相关问题