实例化后是否可以更改C++对象的类?

dwk*_*dwk 41 c++ inheritance

我有一堆类,它们都从公共基类继承相同的属性.基类实现了一些在一般情况下工作的虚函数,而每个子类为各种特殊情况重新实现了这些虚函数.

情况就是这样:我希望这些子类对象的特殊性是可以消耗的.本质上,我想实现一个expend()函数,该函数会导致对象丢失其子类标识,并恢复为基类实例,并在基类中实现通用案例行为.

我应该注意,派生类不会引入任何其他变量,因此基类和派生类在内存中的大小应该相同.

我愿意破坏旧对象并创建一个新对象,只要我可以在同一个内存地址创建新对象,那么现有的指针就不会被破坏.

以下尝试不起作用,并产生一些看似意外的行为.我在这里错过了什么?

#include <iostream>

class Base {
public:
    virtual void whoami() { 
        std::cout << "I am Base\n"; 
    }
};

class Derived : public Base {
public:
    void whoami() {
        std::cout << "I am Derived\n";
    }
};

Base* object;

int main() {
    object = new Derived; //assign a new Derived class instance
    object->whoami(); //this prints "I am Derived"

    Base baseObject;
    *object = baseObject; //reassign existing object to a different type
    object->whoami(); //but it *STILL* prints "I am Derived" (!)

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Jea*_*sen 34

您可以以破坏良好实践和维护不安全代码为代价.其他答案将为您提供令人讨厌的技巧来实现这一目标.

我不喜欢只说"你不应该这样做"的答案,但我想建议可能有更好的方法来实现你所寻求的结果.

@ manni66评论中提出的策略模式是一个很好的策略模式.

您还应该考虑面向数据的设计,因为在您的情况下,类层次结构看起来不是明智的选择.

  • @TitoneMaurice:我听说过使用许多invectives描述的Undefined Behavior,但这是我第一次听到它叫做"酷". (7认同)
  • @Titone任何可以从这样的技巧中获利的程序都是一个c ++程序,它有一些非常非常严重的设计问题.标准没有理由迎合设计糟糕的程序. (3认同)

Sto*_*ica 15

是的,不是.C++类定义作为对象的内存区域的类型.一旦实例化了内存区域,就会设置其类型.你可以尝试的工作围绕类型系统肯定,但是编译器不会让你摆脱它.迟早它会射击你的脚,因为编译器对你违反的类型做出了假设,并且没有办法阻止编译器以便携方式做出这样的假设.

但是有一种设计模式:它是"状态".您可以使用自己的基类将更改提取到其自己的类层次结构中,并且您的对象存储指向此新层次结构的抽象状态库的指针.然后,您可以将这些内容交换到您的心中.


Bat*_*eba 14

不,实例化后无法更改对象的类型.

*object = baseObject;不改变类型object,它只是调用一个编译器生成的赋值运算符.

如果你写过,那将是另一回事

object = new Base;

(记得要delete自然地调用;目前你的代码泄漏了一个对象).

C++ 11以后,您可以将资源从一个对象移动到另一个对象; 看到

http://en.cppreference.com/w/cpp/utility/move

  • 绝对.但是没有什么可以阻止你构建一个管理这种行为的类.你甚至可以重载地址运算符. (2认同)

Ben*_*igt 13

我愿意破坏旧对象并创建一个新对象,只要我可以在同一个内存地址创建新对象,那么现有的指针就不会被破坏.

C++标准在3.8节(对象生命周期)中明确地解决了这个想法:

如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用引用原始对象,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用来操纵新对象 <snip>

哦哇,这正是你想要的.但我没有表现出整个规则.剩下的就是:

如果:

  • 新对象的存储完全覆盖原始对象占用的存储位置,以及
  • 新对象与原始对象的类型相同(忽略顶级cv限定符),和
  • 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,以及
  • 原始对象是类型最派生的对象(1.8),T新对象是类型最派生的对象T(也就是说,它们不是基类子对象).

所以你的想法被语言委员会想到并且特别是非法的,包括偷偷摸摸的解决方案"我有一个正确类型的基类子对象,我只是在它的位置创建一个新对象",最后一个要点停在轨道上.

您可以使用@ RossRidge的答案显示的不同类型的对象替换对象.或者您可以替换对象并继续使用替换前存在的指针.但你不能一起做两件事.

然而,就像着名的名言:"计算机科学中的任何问题都可以通过增加一层间接来解决",这也是正确的.

而不是你建议的方法

Derived d;
Base* p = &d;
new (p) Base();  // makes p invalid!  Plus problems when d's destructor is automatically called
Run Code Online (Sandbox Code Playgroud)

你可以做:

unique_ptr<Base> p = make_unique<Derived>();
p.reset(make_unique<Base>());
Run Code Online (Sandbox Code Playgroud)

如果你将这个指针隐藏在另一个类中,那么你将拥有"设计模式",例如其他答案中提到的State或Strategy.但它们都依赖于一个额外的间接层.


D3C*_*34D 9

我建议你使用策略模式,例如

#include <iostream>

class IAnnouncer {
public:
    virtual ~IAnnouncer() { }
    virtual void whoami() = 0;
};

class AnnouncerA : public IAnnouncer {
public:
    void whoami() override {
        std::cout << "I am A\n";
    }
};

class AnnouncerB : public IAnnouncer {
public:
    void whoami() override {
        std::cout << "I am B\n";
    }
};

class Foo
{
public:
    Foo(IAnnouncer *announcer) : announcer(announcer)
    {
    }
    void run()
    {
        // Do stuff
        if(nullptr != announcer)
        {
            announcer->whoami();
        }
        // Do other stuff
    }
    void expend(IAnnouncer* announcer)
    {
        this->announcer = announcer;
    }
private:
    IAnnouncer *announcer;
};


int main() {
    AnnouncerA a;
    Foo foo(&a);

    foo.run();

    // Ready to "expend"
    AnnouncerB b;
    foo.expend(&b);

    foo.run();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是一种非常灵活的模式,与尝试通过继承处理问题相比至少有一些好处:

  • 您可以稍后通过实现新的播音员轻松更改Foo的行为
  • 您的播音员(以及您的Foos)可以轻松进行单元测试
  • 您可以在代码的其他地方重复使用您的播音员

我建议你看看古老的"作曲与继承"辩论(参见https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose)

PS.你在原帖中漏了一个Derived!看看std :: unique_ptr是否可用.


Ros*_*dge 8

您可以通过放置新的和显式的析构函数调用来实现您真正要求的内容.像这样的东西:

#include <iostream>
#include <stdlib.h>

class Base {
public:
    virtual void whoami() { 
        std::cout << "I am Base\n"; 
    }
};

class Derived : public Base {
public:
    void whoami() {
        std::cout << "I am Derived\n";
    }
};

union Both {
    Base base;
    Derived derived;
};

Base *object;

int
main() {
    Both *tmp = (Both *) malloc(sizeof(Both));
    object = new(&tmp->base) Base;

    object->whoami(); 

    Base baseObject;
    tmp = (Both *) object;
    tmp->base.Base::~Base();
    new(&tmp->derived) Derived; 

    object->whoami(); 

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

然而正如matb所说,这确实不是一个好的设计.我建议重新考虑你想要做的事情.这里的一些其他答案也可以解决你的问题,但我认为你所要求的任何想法都将成为问题.您应该认真考虑设计应用程序,以便在对象类型发生变化时更改指针.

  • 我认为(但没有)投票,因为第一句话是错误的.你不能***改变*对象的类.可以像在代码中一样,用另一个类替换一个对象. (4认同)
  • 第二个`object-> whoami`是未定义的行为.指向base的指针指向被破坏的对象; 据我所知,在没有通过指向派生的指针的情况下,不允许使用别名. (4认同)
  • 我正是因为这个原因而沮丧.我知道什么时候修好了. (3认同)

Sto*_*net 7

您可以通过向基类引入变量,因此内存占用量保持不变.通过设置标志,您可以强制调用派生类或基类实现.

#include <iostream>

class Base {
public:
    Base() : m_useDerived(true)
    {
    }

    void setUseDerived(bool value)
    {
        m_useDerived = value;
    }

    void whoami() {
        m_useDerived ? whoamiImpl() : Base::whoamiImpl();
    }

protected:
    virtual void whoamiImpl() { std::cout << "I am Base\n"; }

private:
    bool m_useDerived;
};

class Derived : public Base {
protected:
    void whoamiImpl() {
        std::cout << "I am Derived\n";
    }
};

Base* object;

int main() {
    object = new Derived; //assign a new Derived class instance
    object->whoami(); //this prints "I am Derived"

    object->setUseDerived(false);
    object->whoami(); //should print "I am Base"

    return 0;
}
Run Code Online (Sandbox Code Playgroud)


ale*_*in0 6

除了其他答案,你可以使用函数指针(或者像它们上面的任何包装器std::function)来实现必要的bevahior:

void print_base(void) {
    cout << "This is base" << endl;
}

void print_derived(void) {
    cout << "This is derived" << endl;
}

class Base {
public:
    void (*print)(void);

    Base() {
        print = print_base;
    }
};

class Derived : public Base {
public:
    Derived() {
        print = print_derived;
    }
};

int main() {
    Base* b = new Derived();
    b->print(); // prints "This is derived"
    *b = Base();
    b->print(); // prints "This is base"
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

此外,此类函数指针方法允许您在运行时更改对象的任何功能,而不是限制您在派生类中实现的某些已定义的成员集.