繁体   English   中英

Base class 持有对 Derived 的引用

[英]Base class holding a reference to Derived

我想这样做:

struct Derived;

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

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

但我似乎得到了不可靠的行为(稍后使用时, m_ref指向无效的衍生事物)。

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

我明白在初始化之前使用这样的引用是无效的,但我看不出对 class 的初始化的更改如何影响对它的引用(因为初始化它不会在 memory 中移动它。 ..)。

我不知道该怎么称呼我正在尝试做的事情,所以我搜索这方面的信息是一片空白......



更新:我无法用一个简单的测试用例重现我的问题,所以看起来可能没问题(尽管我无法证明这一点,但仍然欢迎明确的答案)。 怀疑我的问题是由损坏的复制分配运算符引起的。 这完全是另一回事!

更新 2我的复制构造函数和复制赋值运算符确实是罪魁祸首,现在这似乎工作可靠。 不过,仍然对它是否是明确定义的行为感兴趣。

我认为总的来说你可以这样做,但在构造函数和析构函数中要非常小心。 特别是在Base::~Base中,object 的Derived部分已经被销毁,所以不要在那里使用m_ref

3.8/6 说明了对于已分配但尚未构建的 object 的指针/引用,您可以做什么。 您的代码不会引发左值到右值的转换,或者以其他方式违反规则,所以我认为您很好。 但是,由于您观察到了错误的价值观,因此我很可能错过了一些东西。 否则您的代码可能会很糟糕。

即使您确实违反了这些规则,12.6.2 和 12.7 也会列出您在构建和销毁期间可以做的其他事情。

编辑:啊,8.3.2/4:“应初始化引用以引用有效的 object 或 function。” 您初始化m_ref以引用 object,其构造函数尚未输入。 如果没有进一步研究,我不知道正在构建的 object 是否“有效”,尤其是在构建基础 ZA2F2ED4F8EBC2CBB4C21A29DC40AB6 时,最衍生类型的 object 是否“有效”。 不过,这可能是问题所在。

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

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

那么,这是无效的吗? 如果不是,那么最派生的 object 在基础 class 构造函数调用的开始和派生的 class 构造函数调用的开始之间的某处是否有效? 我不知道,对不起。

3.8/1 说:

类型 T 的 object 的生命周期开始于: — 存储具有适当的 alignment 和类型 T 的大小,并且 — 如果 T 是 classial 类型,则构造函数调用已完成构造函数。12。

3.8/5 说:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage可以使用 object 将位于或曾经位于的位置,但只能以有限的方式使用 这样的指针指的是分配的存储(3.7.3.2),并且使用指针就像指针是 void* 类型一样,是明确定义的。 这样的指针可能会被取消引用,但生成的左值只能以有限的方式使用,如下所述。

“以下”是 3.8/6:

这样的左值指的是分配的存储(3.7.3.2),并且使用不依赖于其值的左值的属性是明确定义的。

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

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

应初始化引用以引用有效的 object 或 function。

似乎没有任何“有效对象”的定义可言。

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


当然,这并不是说它在任何方面都是一个好主意! 它看起来仍然是一个糟糕的设计。

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

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

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


随机其他观察

在编译这个答案时,我注意到了其他一些有趣的事情,这是一个分享它们的好地方。

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

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

如果 object 将是或曾经是非 POD class 类型,则如果 [..] 指针被隐式转换 (4.10) 为指向基本 ZA2F2ED4F8EBC2CBB4C21A21DZ40AB6 类型的指针,则程序具有未定义的行为。

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

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

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

int main() {
   B b;
}

我认为最大的问题是你认为你想做一件事,而实际上你真的想做别的事情。 奇怪吧?

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

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

但是你到底为什么认为 Base 想知道 Derived 呢? 如果您追求的是 static 多态性,那么奇怪的重复模板模式就是您想要的:

template <typename T>
class Base {};

class Derived : public Base<Derived> {};

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

也许您想要 Base 与客户端通信的方式,并认为应该使用 inheritance 来完成? 如果是这样,那么这个观察者式的成语就是你所需要的:

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() { ... }
};

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

我实际上正在实现一个通用的基础 class ,它采用模板参数 class 并从中派生,并基于对派生类型的 function 调用的结果添加“安全布尔”转换。 如果可能的话,我想避免使用虚函数,因为我是一个过早的串行优化器,我真的很关心我想使用它的一些地方的性能。 – 37 分钟前自动填充

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

#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;
}

如果您提前知道没有实现通用布尔检查的派生类,您甚至可以使用模板特化。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM