持有Derived引用的基类

时间:2011-06-05 22:34:35

标签: c++

我想这样做:

struct Derived;

struct Base{
    Derived const& m_ref;
    Base(Derived const& ref) : m_ref(ref){}
};

struct Derived: Base{
    Derived(): Base(*this){}
};

但是我似乎得到了不可靠的行为(以后使用时,m_ref指向无效的东西。)

是否允许在初始化类之前构造对*this的Derived的引用?

我很感激使用这样的引用在它被初始化之前是无效的,但是我没有看到类的初始化的变化会如何影响对它的引用(因为初始化)它不会在记忆中移动它...)。

我不知道该怎么称呼我正在尝试做什么,所以我搜索这方面的信息已经空白......



更新 我无法通过一个简单的测试用例重现我的问题,所以它看起来似乎可能好了(虽然我无法证明它,并且仍然会欢迎一个明确的答案)。 怀疑我的问题是由一个损坏的复制赋值运算符引起的。这完全是另一回事!

更新2 我的拷贝构造函数和拷贝赋值运算符确实应该受到指责,现在这似乎可靠地运行了。仍然感兴趣的是它是否是明确定义的行为。

5 个答案:

答案 0 :(得分:2)

我认为一般来说你可以这样做,但在构造函数和析构函数中要非常小心。特别是,在Base::~Base中,对象的Derived部分已被销毁,因此请勿在其中使用m_ref

答案 1 :(得分:2)

3.8 / 6说明了对于已分配但尚未构造的对象的指针/内存引用可以做什么。你的代码不会引发左值到右值的转换,或者违反规则,所以我认为你很好。既然你正在观察不好的价值观,我可能会错过一些东西。或者你的代码可能不好。

即使您确实违反了这些规则,12.6.2和12.7列出了在构建和销毁期间可以执行的其他操作。

编辑:啊,8.3.2 / 4:“应该初始化引用以引用有效对象 或者你正在初始化m_ref以引用一个尚未输入构造函数的对象。如果没有进一步研究,我不知道在建的对象是否“有效”,特别是大多数派生类型的对象在构造基类时是“有效的”。但这可能是问题所在。

你可能认为没有未构造的对象是“有效的”,但是这将是无效的:

class Foo {
    Foo() {
        Foo &self = *this; // reference initialized to refer to unconstructed object!
    }
};

那么,那是无效的吗?如果不是,那么派生最多的对象在基类构造函数调用的开始和派生类构造函数调用的开始之间的某处是否有效?我不知道,抱歉。

答案 2 :(得分:2)

3.8 / 1说:

  

T类型对象的生命周期   从开始: - 使用正确的存储   T型的对齐和大小是   获得,并且 - 如果T是类类型   用一个非平凡的构造函数(12.1),   构造函数调用已完成

3.8 / 5说:

  

在对象的生命周期开始之前但在对象占用的存储空间被分配之后   或者,在对象的生命周期结束之后和对象占用的存储之前   重用或释放,任何指向对象所在或存在的存储位置的指针   可以使用,但只能以有限的方式使用。这样的指针指的是分配存储(3.7.3.2),并使用   指针好像指针的类型为void *,是明确定义的。 这样的指针可以被解除引用但是   结果左值只能以有限的方式使用,如下所述。

“以下”是3.8 / 6:

  

这样的左值是指分配的存储(3.7.3.2),并使用左值的属性   依赖于它的价值是明确的。

...然后列出你不能做的事情。绑定到对相同派生类型的引用不在其中。

我在其他地方找不到任何可能导致您的代码无效的内容。值得注意的是,尽管8.3.2 / 4中有以下短语:

  

引用应初始化为   引用有效的对象或函数。

似乎没有任何关于“有效对象”的定义。

所以,经过多次来回,我必须得出结论认为这是合法的


当然,这并不是说这绝对是一个好主意! 它看起来仍然是一个糟糕的设计。

例如,如果您稍后更改基础构造函数并且以下任何一项变得相关(再次从3.8 / 6开始):

  • 左值用于访问非静态数据成员或调用对象的非静态成员函数
  • 将左值隐式转换(4.10)为对基类类型的引用
  • 左值用作static_cast(5.2.9)的操作数(转换最终为char&unsigned char&
  • 时除外)
  • 左值用作dynamic_cast(5.2.7)的操作数或typeid的操作数。

...那么你的程序将是未定义的,编译器可能不会发出任何诊断信息!


随机其他观察

在编写这个答案的过程中,我注意到了其他一些有趣的事情,这和任何人分享它们一样好。

首先,9.3.2似乎在{em> ctor-initializer 中意外地未指定this的类型。奇异!

其次,指针设置的标准为3.8 / 5(与我从3.8 / 6中引用的列表不同)包括:

  

如果对象是或者是对象   非POD类类型,程序有   未定义的行为,如果[..]   指针被隐式转换(4.10)   指向基类类型的指针。

我相信这会使以下看似无害的代码未定义:

struct A {
   A(A* ptr) {}
};

struct B : A {
   B() : A(this) {}
};

int main() {
   B b;
}

答案 3 :(得分:1)

我认为这方面的一个大问题是你认为你想要做一件事,而实际上你真的想做别的事情。奇怪,是吗?

  

是否允许在初始化类之前构造对Derived的引用?

是的,只要你没有使用它(除了存储对它的引用之外的任何东西)在Base构造函数的范围内,并记住在~Base中Derived在Base之前被销毁。

但是为什么你认为Base想知道Derived?如果它是你所追求的静态多态,那么curiously recurring template pattern就是你想要的:

template <typename T>
class Base {};

class Derived : public Base<Derived> {};

但我并不认为这就是你的目标。

也许您想要一种方法让Base与客户进行通信并认为应该通过继承来完成?如果是这样,那么这个观察者的习语是你需要的:

class Client
{
public: 
    virtual void Behavior() = 0;

protected:
    ~Client() {}
};

class Base
{
    Client& client_;

public:
    Base(Client& client) : client_(client) {}
};

class Implementor : public Client
{
public:
    Implementor() : Base(*this) {}

    virtual void Behavior() { ... }
};

如果不是您想要的,那么您需要重新考虑您的设计。

答案 4 :(得分:0)

  

我实际上是在实现一个通用基类,它接受模板参数类并从中派生,并根据派生类型的函数调用结果添加“安全bool”转换。如果可能的话,我想避免使用虚函数,因为我是一个串行的过早优化器我真的很关心我想要使用它的一些地方的性能。 - 37分钟前自动装配

您不需要对Derived类的引用。您的类派生自模板参数。只需使用常用方法即可。

#include <iostream>

template <class T>
class Base : public T
{
public:
    bool operator!() const 
    {
        return !this->isOk();
    }
};

class TemplateClass
{

public:
    bool isOk() const
    {
        return true;
    }
};

int main (int argc, char* argv[])
{
    Base<TemplateClass> myClass;

    if (!!myClass)
    {
        std::cout << "ok" << std::endl;
    }
    else
    {
        std::cout << "not ok" << std::endl;
    }

    return 0;
}

如果您事先知道未实现常见布尔检查的派生类,您甚至可以使用模板专门化。