c++ - 用放置 new 覆盖内存中的对象

标签 c++ placement-new

我有一个对象,我想将其“转换”为另一个对象。为此,我在第一个对象上使用了 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();
  }
};

您可以看到,当使用 transform 调用 Dog 时,它会在给定地址之上创建一个新的 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

我的问题是:

  1. 此操作是否被认为是安全的,还是会使对象处于不稳定状态?
  2. 转换后我调用 dog.voice()。它正确地打印出名称 CAT(它现在是一只猫),但仍然写出 WOOF I am a,即使我认为它应该调用 voice方法? (你可以看到我调用了同样的方法,但是通过地址((&dog)->voice()),一切正常。

最佳答案

Does this operation considered safe, or does it leave the object in unstable state?

此操作不安全,会导致未定义的行为。 CatDog 有非平凡的析构函数,所以在你可以重用存储 catdog 之前你必须调用它们的析构函数,因此前一个对象被正确清理。

After the transform I call dog.voice(). I prints correctly the CAT name (it is now a cat), but still writes WOOF I am a, even tough I would have thought that it should call the Cat's voice method? (You can see is that I call the same method but by the address ((&dog)->voice()), everything is working properly.

dog.transform(&dog); 之后使用 dog.voice(); 是未定义的行为。由于您已重用其存储而不破坏它,因此您有未定义的行为。假设您确实在 transform 中销毁了 dog 以摆脱那些您仍未摆脱困境的未定义行为。在 dog 被销毁后使用它是未定义的行为。您需要做的是捕获指针放置新返回并从那时起使用该指针。您还可以在 dog 上使用 std::launderreinterpret_cast 到您将其转换为的类型,但它不值得,因为您丢失了所有封装.


您还需要确保在使用placement new 时,您使用的对象对于您正在构建的对象来说足够大。在这种情况下,应该是因为类是相同的,但是比较大小的 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;
}

产生输出

Cat says: MEOW I am a CAT
Dog says: WOOF I am a DOG
Dog says: MEOW I am a CAT
Dog address says: MEOW I am a CAT

而且这是安全和便携的。

关于c++ - 用放置 new 覆盖内存中的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55711220/

相关文章:

c++ - 我还应该传递类成员数据作为速度引用吗?

c++ - "placement new"有什么用?

c++ - 如何直接将一大块内存读入 std::vector?

c++ - 这第二个是什么新东西?

c++ - 编译器不编译以下行 (C++)

c++ - 如何使用make更高效地编译c文件

c++ - 为模板参数推断层次结构中的最低类型

c++ - 错误 : use of deleted function ‘std::thread::thread(const std::thread&)'

c++ - Consexpr 安置新?

c++ - 我们真的需要放置新表达式吗?