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

我有一堆类都从一个共同的基类inheritance相同的属性。 基类实现了一些在一般情况下工作的虚拟函数,而每个子类重新实现了各种特殊情况下的虚拟函数。

情况如下:我希望这些分类对象的特殊性是可以消耗的。 本质上,我想要实现一个expend()函数,这个函数会导致一个对象失去它的子类标识,然后恢复成基类实例,并且在基类中实现一般的行为。

我应该注意到派生类不会引入任何额外的variables,所以基类和派生类在内存中应该是相同的大小。

我打算销毁旧的对象,并创build一个新的,只要我可以在同一个内存地址创build新的对象,所以现有的指针不会被打破。

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

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

您可以以破坏良好的做法和维护不安全的代码为代价。 其他答案将为您提供肮脏的技巧来实现这一点。

我不喜欢只是说“你不应该这样做”的答案,但我想build议可能有一个更好的方法来实现你所寻求的结果。

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

你还应该考虑数据导向devise ,因为在你的情况下,类层次结构看起来不是一个明智的select。

是和不是。 C ++类定义了作为对象的内存区域的types。 一旦内存区域被实例化,其types被设置。 你可以尝试解决types系统,但编译器不会让你摆脱它。 迟早它会把你击倒在脚下,因为编译器对你所违反的types做了一个假设,而且没有办法阻止编译器以便携的方式做出这样的假设。

但是有一个devise模式:这是“国家”。 您可以使用自己的基类将自己的类层次结构中的更改提取出来,并且您的对象将存储指向此新层次结构的抽象状态库的指针。 然后你可以把这些交换到你的心中。

不,一旦实例化就不可能改变对象的types。

*object = baseObject; 不会改变objecttypes ,只是调用一个编译器生成的赋值操作符。

如果你写的话,这将是一个不同的问题

object = new Base;

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

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

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

我打算销毁旧的对象,并创build一个新的,只要我可以在同一个内存地址创build新的对象,所以现有的指针不会被打破。

C ++标准在第3.8节(对象生存期)中明确地阐述了这个想法:

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

哇,这正是你想要的。 但我没有显示整个规则。 剩下的是:

如果

  • 新对象的存储正好覆盖原对象占用的存储位置
  • 新对象与原始对象的types相同(忽略顶级cv限定符) ,并且
  • 原始对象的types不是const限定的,并且如果类types不包含任何types为const限定的非静态数据成员或引用types,并且
  • 原始对象是typesT的最多派生对象(1.8),新对象是typesT的最派生对象(也就是说,它们不是基类子对象)。

所以你的想法已经被语言委员会想到了,特别是非法的,包括偷偷摸摸的解决方法,“我有一个正确的types的基类子对象,我只是在它的地方做一个新的对象”,这是最后一个要点停在它的轨道上。

你可以用@ RossRidge的答案显示的不同types的对象replace一个对象。 或者你可以replace一个对象,并继续使用replace之前存在的指针。 但是你们不能一起做。

然而,就像这句名言:“计算机科学中的任何问题都可以通过增加一层间接寻址来解决” ,在这里也是如此。

而不是你build议的方法

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

你可以做:

 unique_ptr<Base> p = make_unique<Derived>(); p.reset(make_unique<Base>()); 

如果你将这个指针隐藏在另一个类的内部,你将会看到其他答案中提到的“devise模式”,例如State或者Strategy。 但是他们都依赖于一个额外的间接水平。

你可以做你真正要求放置新的和明确的析构函数调用。 像这样的东西:

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

然而,正如matb所说,这确实不是一个好的devise。 我会build议重新考虑你想要做的事情。 这里的其他一些答案也可能解决你的问题,但我认为任何你想要什么的概念将成为kludge。 您应该认真考虑devise您的应用程序,以便在对象types更改时更改指针。

我build议你使用战略模式,例如

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

这是一个非常灵活的模式,与通过inheritance来处理这个问题至less有一些好处:

  • 稍后通过实施新的播音员,您可以轻松更改Foo的行为
  • 你的播音员(和你的Foos)很容易进行unit testing
  • 您可以在代码中的其他地方重复使用您的播音员

我build议你看一下这个古老的“构成与inheritance”辩论(参见https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose

PS。 您已经在原始文章中泄露了Derived! 看看std :: unique_ptr是否可用。

你可以通过向基类引入一个variables,所以内存占用情况保持不变。 通过设置标志,您可以强制调用派生类或基类实现。

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

除了其他的答案,你可以使用函数指针(或任何包装,如std::function )来实现必要的优化:

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

另外,这样的函数指针方法可以让你在运行时更改对象的任何函数,而不是限制你在派生类中实现的一些已经定义的成员集合。

在你的程序中有一个简单的错误。 您分配的对象,但不是指针:

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

现在,您将baseObject分配给使用Base对象覆盖Derived对象的*object对象。 不过,这样做确实很好,因为您正在使用Basetypes的对象覆盖Derivedtypes的对象。 默认的赋值操作符只是分配所有的成员,在这种情况下什么都不做。 该对象不能改变它的types,然后仍然是Derived对象。 一般来说,这会导致严重的问题,例如对象切片。

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

如果你只是分配指针,它将按预期工作,但你只有两个对象,一个是Derivedtypes和一个Basetypes,但是我认为你需要更多的dynamic行为。 这听起来像你可以实现作为装饰者的特殊性。

你有一个具有一些操作的基类,以及一些改变/修改/扩展该操作的基类行为的派生类。 由于它是基于构图,所以可以dynamic改变。 窍门是在Decorator实例中存储基类引用,并将其用于所有其他function。

 class Base { public: virtual void whoami() { std::cout << "I am Base\n"; } virtual void otherFunctionality() {} }; class Derived1 : public Base { public: Derived1(Base* base): m_base(base) {} virtual void whoami() override { std::cout << "I am Derived\n"; // maybe even call the base-class implementation // if you just want to add something } virtual void otherFunctionality() { base->otherFunctionality(); } private: Base* m_base; }; Base* object; int main() { Base baseObject; object = new Derived(&baseObject); //assign a new Derived class instance object->whoami(); //this prints "I am Derived" // undecorate delete object; object = &baseObject; object->whoami(); return 0; } 

像Strategy这样的替代模式实现了不同的用例, 解决不同的问题。 这可能是很好的阅读模式文档,特别关注的意图和动机部分。

我会考虑规范你的types。

 class Base { public: virtual void whoami() { std::cout << "Base\n"; } std::unique_ptr<Base> clone() const { return std::make_unique<Base>(*this); } virtual ~Base() {} }; class Derived: public Base { virtual void whoami() overload { std::cout << "Derived\n"; }; std::unique_ptr<Base> clone() const override { return std::make_unique<Derived>(*this); } public: ~Derived() {} }; struct Base_Value { private: std::unique_ptr<Base> pImpl; public: void whoami () { pImpl->whoami(); } template<class T, class...Args> void emplace( Args&&...args ) { pImpl = std::make_unique<T>(std::forward<Args>(args)...); } Base_Value()=default; Base_Value(Base_Value&&)=default; Base_Value& operator=(Base_Value&&)=default; Base_Value(Base_Value const&o) { if (o.pImpl) pImpl = o.pImpl->clone(); } Base_Value& operator=(Base_Value&& o) { auto tmp = std::move(o); swap( pImpl, tmp.pImpl ); return *this; } }; 

现在, Base_Value在语义上是一种多态的值types。

 Base_Value object; object.emplace<Derived>(); object.whoami(); object.emplace<Base>(); object.whoami(); 

你可以将一个Base_Value实例包装在一个智能指针中,但是我不打扰。

我并不反对这样的build议,即这不是一个好的devise,但另一个安全的方法是使用一个可以容纳任何你想切换的类的工会,因为标准保证它可以安全的他们。 这是一个封装联合体内所有细节的版本:

 #include <cassert> #include <cstdlib> #include <iostream> #include <new> #include <typeinfo> class Base { public: virtual void whoami() { std::cout << "I am Base\n"; } virtual ~Base() {} // Every base class with child classes that might be deleted through a pointer to the // base must have a virtual destructor! }; class Derived : public Base { public: void whoami() { std::cout << "I am Derived\n"; } // At most one member of any union may have a default member initializer in C++11, so: Derived(bool) : Base() {} }; union BorD { Base b; Derived d; // Initialize one member. BorD(void) : b() {} // These defaults are not used here. BorD( const BorD& ) : b() {} // No per-instance data to worry about! // Otherwise, this could get complicated. BorD& operator= (const BorD& x) // Boilerplate: { if ( this != &x ) { this->~BorD(); new(this) BorD(x); } return *this; } BorD( const Derived& x ) : d(x) {} // The constructor we use. // To destroy, be sure to call the base class' virtual destructor, // which works so long as every member derives from Base. ~BorD(void) { dynamic_cast<Base*>(&this->b)->~Base(); } Base& toBase(void) { // Sets the active member to b. Base* const p = dynamic_cast<Base*>(&b); assert(p); // The dynamic_cast cannot currently fail, but check anyway. if ( typeid(*p) != typeid(Base) ) { p->~Base(); // Call the virtual destructor. new(&b) Base; // Call the constructor. } return b; } }; int main(void) { BorD u(Derived{false}); Base& reference = ud; // By the standard, u, ub and ud have the same address. reference.whoami(); // Should say derived. u.toBase(); reference.whoami(); // Should say base. return EXIT_SUCCESS; } 

一个简单的方法来获得你想要的可能是保留一个Base *的容器,并根据需要单独replace项目与newdelete 。 (还记得要声明你的析构函数是virtual !这对于多态类是很重要的,所以你可以为这个实例调用正确的析构函数,而不是基类的析构函数。)这可以为更小的类实例节省一些额外的字节。 尽pipe如此,您仍然需要使用智能指针来获取安全的自动删除。 智能指针指向dynamic内存的一个优点是您不必在堆上分配或释放更多的对象,但可以重新使用您拥有的内存。

免责声明:这里的代码是为了理解一个想法而提供的,而不是在生产中实现的。

你正在使用inheritance。 它可以实现3件事情:

  • 添加字段
  • 添加方法
  • replace虚拟方法

在所有这些function中,您只使用最后一个function。 这意味着你实际上并不需要依赖inheritance。 您可以通过其他方式获得相同的结果。 最简单的方法就是自己贴上“types”的标签 – 这样可以让你在运行中改变它:

 #include <stdexcept> enum MyType { BASE, DERIVED }; class Any { private: enum MyType type; public: void whoami() { switch(type){ case BASE: std::cout << "I am Base\n"; return; case DERIVED: std::cout << "I am Derived\n"; return; } throw std::runtime_error( "undefined type" ); } void changeType(MyType newType){ //insert some checks if that kind of transition is legal type = newType; } Any(MyType initialType){ type = initialType; } }; 

没有inheritance的“types”是你的任何你想做的事情。 你可以在任何时候改变它适合你。 由于这种权力也是责任:编译器将不再确保types是正确的,甚至根本不设置。 你必须确保它,否则你将很难debugging运行时错误。

你也可以用inheritance来包装它,例如。 获取现有代码的替代scheme:

 class Base : Any { public: Base() : Any(BASE) {} }; class Derived : public Any { public: Derived() : Any(DERIVED) {} }; 

OR(稍微丑陋):

 class Derived : public Base { public: Derived : Base() { changeType(DERIVED) } }; 

这个解决scheme很容易实现,易于理解。 但是随着更多的选项和更多的代码在每个path它变得非常混乱。 所以,第一步就是将实际的代码从开关中重构成独立的函数。 哪里比Derivied类更好地保持?

 class Base { public: static whoami(Any* This){ std::cout << "I am Base\n"; } }; class Derived { public: static whoami(Any* This){ std::cout << "I am Derived\n"; } }; /*you know where it goes*/ switch(type){ case BASE: Base:whoami(this); return; case DERIVED: Derived:whoami(this); return; } 

然后你可以用一个外部类来replace交换机,通过虚拟inheritance和TADA实现它! 正如其他人所说的,我们已经改变了战略模式:)

底线是:无论你做什么,你都不会inheritance主类。

你不能在实例化之后改变对象的types,就像你在你的例子中看到的那样,你有一个指向Base类(基类的types)的指针,所以这个types被卡住了,直到结束。

  • 基指针可以指向上或下对象并不意味着改变其types:

     Base* ptrBase; // pointer to base class (type) ptrBase = new Derived; // pointer of type base class `points to an object of derived class` Base theBase; ptrBase = &theBase; // not *ptrBase = theDerived: Base of type Base class points to base Object. 
  • 指针强大,灵活,function强大,所以你应该谨慎处理。

在你的例子中,我可以写:

 Base* object; // pointer to base class just declared to point to garbage Base bObject; // object of class Base *object = bObject; // as you did in your code 

高于这是一个灾难分配值未分配的指针。 该程序将崩溃。

在你的例子中,你通过首先分配的内存逃脱了崩溃:

 object = new Derived; 

将一个子类对象的value and not address赋给基类value and not address一个好主意。 但是在内置中,您可以考虑这个例子:

 int* pInt = NULL; int* ptrC = new int[1]; ptrC[0] = 1; pInt = ptrC; for(int i = 0; i < 1; i++) cout << pInt[i] << ", "; cout << endl; int* ptrD = new int[3]; ptrD[0] = 5; ptrD[1] = 7; ptrD[2] = 77; *pInt = *ptrD; // copying values of ptrD to a pointer which point to an array of only one element! // the correct way: // pInt = ptrD; for(int i = 0; i < 3; i++) cout << pInt[i] << ", "; cout << endl; 

所以结果不像你猜想的那样。

我有2个解决scheme。 一个简单的不保存内存地址,一个保存内存地址。

两者都要求您提供从基础派生的向下转换,这对您的情况不是问题。

 struct Base { int a; Base(int a) : a{a} {}; virtual ~Base() = default; virtual auto foo() -> void { cout << "Base " << a << endl; } }; struct D1 : Base { using Base::Base; D1(Base b) : Base{ba} {}; auto foo() -> void override { cout << "D1 " << a << endl; } }; struct D2 : Base { using Base::Base; D2(Base b) : Base{ba} {}; auto foo() -> void override { cout << "D2 " << a << endl; } }; 

对于前者,您可以创build一个智能指针,它可以看似改变Derived(和base)类之间的数据:

 template <class B> struct Morpher { std::unique_ptr<B> obj; template <class D> auto morph() { obj = std::make_unique<D>(*obj); } auto operator->() -> B* { return obj.get(); } }; int main() { Morpher<Base> m{std::make_unique<D1>(24)}; m->foo(); // D1 24 m.morph<D2>(); m->foo(); // D2 24 } 

魔法在

 m.morph<D2>(); 

这改变了保留数据成员的被保存对象(实际上使用了cast)。


如果需要保留内存位置 ,则可以使用缓冲区和放置位置来替代unique_ptr 。 这是一个更多的工作更多的关注付出,但它给你正是你所需要的:

 template <class B> struct Morpher { std::aligned_storage_t<sizeof(B)> buffer_; B *obj_; template <class D> Morpher(const D &new_obj) : obj_{new (&buffer_) D{new_obj}} { static_assert(std::is_base_of<B, D>::value && sizeof(D) == sizeof(B) && alignof(D) == alignof(B)); } Morpher(const Morpher &) = delete; auto operator=(const Morpher &) = delete; ~Morpher() { obj_->~B(); } template <class D> auto morph() { static_assert(std::is_base_of<B, D>::value && sizeof(D) == sizeof(B) && alignof(D) == alignof(B)); obj_->~B(); obj_ = new (&buffer_) D{*obj_}; } auto operator-> () -> B * { return obj_; } }; int main() { Morpher<Base> m{D1{24}}; m->foo(); // D1 24 m.morph<D2>(); m->foo(); // D2 24 m.morph<Base>(); m->foo(); // Base 24 } 

这当然是绝对的裸骨。 您可以添加移动计算器,解除引用操作符等

您的任务只分配成员variables,而不是用于虚拟成员函数调用的指针。 您可以轻松地用完整的内存副本replace它:

 //*object = baseObject; //this assignment was wrong memcpy(object, &baseObject, sizeof(baseObject)); 

请注意,就像您尝试赋值一样,这会将*object成员variablesreplace为新构造的baseObject成员variables – 可能不是您真正想要的,所以您必须首先将原始成员variables复制到新的baseObject,在memcpy之前的赋值操作符或拷贝构造函数,即

 Base baseObject = *object; 

有可能只复制虚拟函数表指针,但是依赖关于编译器如何存储它的内部知识,所以不build议这样做。

如果将对象保存在相同的内存地址并不重要,那么更简单和更好的方法是相反的 – 构造一个新的基础对象并复制原始对象的成员variables – 即使用复制构造函数。

 object = new Base(*object); 

但是你也必须删除原来的对象,所以上面的一行代码是不够的 – 你需要记住另一个variables中的原始指针,以便删除它,等等。如果你有多个引用该原始对象,你需要更新它们,有时这可能是相当复杂的。 那么memcpy方式就更好了。

如果某些成员variables本身是指向在主对象的构造函数/析构函数中创build/删除的对象的指针,或者如果它们具有更专门化的赋值运算符或其他自定义逻辑,那么您将得到更多的工作。对于微不足道的成员variables,这应该足够好。