我有一个想要“转换”为另一个对象的对象。为此,我在第一个对象上使用placement new
,该对象在其自身地址的顶部创建了另一种类型的新对象。
考虑以下代码:
#include <string>
#include <iostream>
class Animal {
public:
virtual void voice() = 0;
virtual void transform(void *animal) = 0;
virtual ~Animal() = default;;
};
class Cat : public Animal {
public:
std::string name = "CAT";
void voice() override {
std::cout << "MEOW I am a " << name << std::endl;
}
void transform(void *animal) override {
}
};
class Dog : public Animal {
public:
std::string name = "DOG";
void voice() override {
std::cout << "WOOF I am a " << name << std::endl;
}
void transform(void *animal) override {
new(animal) Cat();
}
};
您会看到,用Dog
调用transform
时,它将在给定地址的顶部创建一个新的Cat
。
接下来,我将使用自己的地址呼叫Dog::transform
:
#include <iostream>
#include "Animals.h"
int main() {
Cat cat{};
Dog dog{};
std::cout << "Cat says: ";
cat.voice() ;
std::cout << "Dog says: ";
dog.voice();
dog.transform(&dog);
std::cout << "Dog says: ";
dog.voice();
std::cout << "Dog address says: ";
(&dog)->voice();
return 0;
}
结果是:
Cat says: MEOW I am a CAT
Dog says: WOOF I am a DOG
Dog says: WOOF I am a CAT
Dog address says: MEOW I am a CAT
我的问题是:
dog.voice()
。它可以正确打印名称CAT
(现在是一只猫),但是仍然写着WOOF I am a
,即使我以为它应该调用Cat
的{{1}}方法? (您可以看到,我调用了相同的方法,但是通过地址(voice
),一切正常。答案 0 :(得分:14)
此操作是否安全,还是会使对象处于不稳定状态?
此操作不安全,并且会导致未定义的行为。 Cat
和Dog
具有非琐碎的析构函数,因此在重新使用存储cat
和dog
之前,必须调用它们的析构函数,以便正确清除先前的对象。 / p>
转换后,我叫
dog.voice()
。我正确打印了CAT
的名字(现在是猫),但仍然写着WOOF I am a
,即使我很难理解,它也应该称呼Cat
的{{1}}方法? (您可以看到,我调用了相同的方法,但是通过地址(voice
),一切正常。
在(&dog)->voice()
之后使用dog.voice();
是未定义的行为。由于您在不破坏存储的情况下重复使用了其存储,因此您具有未定义的行为。假设您确实销毁了dog.transform(&dog);
中的dog
,以摆脱那些尚未定义的行为。 transform
被销毁后使用是未定义的行为。您需要做的就是捕获指针放置新的返回值,然后使用该指针。您还可以将dog
上的std::launder
与dog
一起使用,以将其转换为要转换的类型,但这不值得,因为您丢失了所有封装。
还需要确保在使用new放置时,要使用的对象对于要构造的对象足够大。在这种情况下,应该是因为类是相同的,但是reinterpret_cast
比较大小将保证这样做,并在编译不正确时停止编译。
解决此问题的一种方法是创建一个不同的动物类来充当动物类的持有人(在下面的示例代码中,我将其重命名为static_assert
)。这使您可以封装Animal_Base
代表的对象类型的更改。将您的代码更改为
Animal
,然后将class Animal_Base {
public:
virtual void voice() = 0;
virtual ~Animal_Base() = default;
};
class Cat : public Animal_Base {
public:
std::string name = "CAT";
void voice() override {
std::cout << "MEOW I am a " << name << std::endl;
}
};
class Dog : public Animal_Base {
public:
std::string name = "DOG";
void voice() override {
std::cout << "WOOF I am a " << name << std::endl;
}
};
class Animal
{
std::unique_ptr<Animal_Base> animal;
public:
void voice() { animal->voice(); }
// ask for a T, make sure it is a derived class of Animal_Base, reset pointer to T's type
template<typename T, std::enable_if_t<std::is_base_of_v<Animal_Base, T>, bool> = true>
void transform() { animal = std::make_unique<T>(); }
// Use this to say what type of animal you want it to represent. Doing this instead of making
// Animal a temaplte so you can store Animals in an array
template<typename T, std::enable_if_t<std::is_base_of_v<Animal_Base, T>, bool> = true>
Animal(T&& a) : animal(std::make_unique<T>(std::forward<T>(a))) {}
};
调整为
main
产生输出
int main()
{
Animal cat{Cat{}};
Animal dog{Dog{}};
std::cout << "Cat says: ";
cat.voice() ;
std::cout << "Dog says: ";
dog.voice();
dog.transform<Cat>();
std::cout << "Dog says: ";
dog.voice();
std::cout << "Dog address says: ";
(&dog)->voice();
return 0;
}
这是安全且便携的。
答案 1 :(得分:6)
1)不,由于以下原因,这是不安全的:
2)我在MSVC2015上观察到dog.voice()
将调用Dog::voice
而不检查实际的虚拟表。在第二种情况下,它将检查已修改为Cat::voice
的虚拟表。但是,根据其他用户的经验,某些其他编译器可能会进行一些优化,并在所有情况下直接调用与声明匹配的方法。
答案 2 :(得分:6)
此代码至少存在三个问题:
<?php
namespace Gloudemans\Shoppingcart\Facades;
use Illuminate\Support\Facades\Facade;
class Cart extends Facade {
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'cart';
}
}
?>
对象的存储已被重用之后,您可以使用它。