【问题标题】:Curious case of C++ Object slicing behaviour on Base class static reference data member基类静态引用数据成员上 C++ 对象切片行为的奇特案例
【发布时间】:2016-07-22 15:58:41
【问题描述】:

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

这是我的基类

// 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 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

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

我的意思是当我打电话给 r.doSomething() 做某事时,我得到了派生类的 doSomething。但

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

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

任何形式的澄清将不胜感激。 谢谢。

【问题讨论】:

  • 您不能放弃引用。如果看起来像你,那么正在发生的事情就是切片。
  • 谢谢伙计。我犯了一个愚蠢的错误(从连续 2 晚开始编码,我想我应该只是睡觉)再次感谢。

标签: c++ singleton static-members object-slicing


【解决方案1】:

当你这样做时

Base::base=Derived::derived;

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

base 仍然是 Base 类型,并且永远不会改变,因为引用只能被初始化一次,你可以这样做

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

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

【讨论】:

  • 哦,我完全忘记了引用只能被初始化一次的事实。再次感谢。
  • @Dr.Xperience 没问题。很高兴能提供帮助。
【解决方案2】:

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

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

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

当您随后进行分配时,您将不再更改引用,但您会将对象复制到 base 引用的对象中(它的类型是 base,因此是切片!)。

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

【讨论】:

  • Re“因此切片”,没有数据成员,因此无法检测到切片。
  • 谢谢伙计,我知道指针方法。只是避免它具有最少的跟踪数量(英特尔跟踪/预解码)。再次感谢。
  • @Cheersandhth.-Alf 可能没有数据成员,但有虚函数。由于分配是关于普通对象的,因此 Derived 类型丢失了,因此它不再使用预期的虚函数。我不会说英语:当某些信息丢失时,我会使用“切片”using this definition,但也许你可以提出更好的措辞。
  • @Christophe:不,分配不会影响虚函数。从技术上讲,可以说它们不受影响的事实是切片的一个例子。但它无法检测到,因为效果根本没有效果。
  • @Cheersandhth.-Alf 在赋值后 base.DoSomething() 将调用base::DoSomething() 而不是Derived::DoSomething() 就像derived 那样。所以它在副本中“丢失”了。我们在这里玩文字
【解决方案3】:

引用是指对象所引用的所有目的。赋值给引用就是赋值给被引用的对象。在您的情况下,对象没有数据成员,因此没有效果。

特别是没有可检测到的切片。

声明

Base& r = Derived::derived;

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

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

此后不能更改引用。


在其他新闻中:

  • 全局变量(引用)存在静态初始化顺序失败的风险。它们只会抵消单例 getter 函数的优势。使用全局函数,这些函数只是无目的的增加冗长。

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

【讨论】:

  • 这只是一个示例。我的实际案例在派生类中有很多成员。感谢帮助。我犯了一个愚蠢的错误。
  • 我的项目只有两个单例。他们充当服务提供者。虽然对于司机服务提供商来说开销很大。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-06-01
  • 2018-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多