Base class 静态引用数据成员上 C++ 对象切片行为的奇怪案例

Curious case of C++ Object slicing behaviour on Base class static reference data member

我遇到了一个奇怪的对象切片案例。我正在做一个需要单例 classes 的项目,所以我的 Base 和派生 class 都是单例。以下示例案例描述了我的情况。

这是我的基地class

// Base.h
class Base
{
    public:

        static Base& base;

        virtual void doSomething(){ cout<<"Base Do Something"<<endl; }

    protected:

        Base();
        virtual ~Base();
        static Base& getBaseInstance();

    private:
};

//Base.cpp
Base::Base()
{
    //ctor
}

Base::~Base()
{
    //dtor
}

Base& Base::getBaseInstance()
{
    static Base object;
    return object;
}
Base& Base::base=Base::getBaseInstance();

这是我的衍生Class

class Derived: public Base
{
    public:

    static Derived& derived;
    virtual void doSomething(){ cout<<"Derive Do Something"<<endl; }

  static Derived& getDerivedInstance();
    protected:
        Derived();
        virtual ~Derived();

    private:
};

Derived::Derived()
{
    //ctor
}

Derived::~Derived()
{
    //dtor
}

Derived& Derived::derived=Derived::getDerivedInstance();

Derived& Derived::getDerivedInstance()
{
    static Derived object;
    return object;
}

最后这是我的主要功能

int main()
{
    cout << "Hello world!" << endl;
    Base::base.doSomething();
    Derived::derived.doSomething();

    Base::base=Derived::derived;

    Base::base.doSomething();

    Base::base=Derived::getDerivedInstance();

    Base::base.doSomething();

    Base& r = Derived::derived;

    r.doSomething();

    Base::base=Derived::getDerivedInstance();
    Base::base.doSomething();
    return 0;
}

我得到了以下输出

Hello world!
Base Do Something
Derive Do Something
Base Do Something
Base Do Something
Derive Do Something
Base Do Something

所以我的问题是,既然对象切片不应该对引用起作用,那么为什么我不能用 Derived 对象覆盖我作为 Base class 的静态数据成员创建的 Base::base 引用?虽然这适用于 Base& r = Derived::derived;

我的意思是当我调用 do something with r.doSomething() 时,我得到了 Derived class 的 doSomething。但

并非如此
    Base::base=Derived::derived;    
    Base::base.doSomething();

    Base::base=Derived::getDerivedInstance();    
    Base::base.doSomething();

如有任何澄清,我们将不胜感激。 谢谢。

引用是用于所有目的的对象。给引用赋值就是给引用的对象赋值。在你的例子中,对象没有数据成员,所以没有效果。

特别是没有检测到切片。

宣言

Base& r = Derived::derived;

…是一个非常不同的情况:初始化,而不是赋值。

初始化使引用指向指定的对象。

此后无法更改引用。


在其他新闻中:

  • 全局变量(引用)运行 静态初始化顺序失败的风险。它们只会抵消单例 getter 函数的优势。对于全局变量,函数只是无目的的冗长。

  • 具有数据成员的可变单例允许代码中广泛分离且看似不相关的部分之间进行通信。这使得很难依赖关于当前状态以及什么影响什么的任何假设。这也是为什么全局变量在所有编程语言中都被视为 Evil™ 的原因,因此请谨慎使用单例。

当你这样做时

Base::base=Derived::derived;

您没有将基引用设置为引用派生的 class。这是赋值运算符,它所做的只是将 derivedBase 部分赋值给 base.

base 仍然是 Base 类型,并且永远不会更改,因为引用只能初始化一次,您可以使用

Base& Base::base=Base::getBaseInstance();

如果您想要这种重新分配行为,您将需要使用指针类型。

Base::base 是静态引用。你在这里初始化它:

Base& Base::base=Base::getBaseInstance();

从此时开始(即在执行使用 base 的代码之前),base 指的是基础实例(即在 Base::getBaseInstance() 中声明的静态实例)。

然后执行赋值时,您将不再更改引用,但会将对象复制到 base 引用的对象中(它是 base 类型,因此是切片!)。

一种解决方法是使 Base::base 成为一个指针。这样,您就可以更改它并指向派生对象。但是,这是否适合我们的单例方法(因为那样您将在某处拥有基础对象和派生对象)。