【问题标题】:How do I manually delete an instance of a class?如何手动删除类的实例?
【发布时间】:2016-07-11 14:09:13
【问题描述】:

如何手动删除类的实例?

例子:

#include <iostream>
#include <cstring>

class Cheese {
private:
    string brand;
    float cost;
public:
    Cheese(); // Default constructor
    Cheese(string brand, float cost); // Parametrized constructor
    Cheese(const Cheese & rhs); // Copy construtor
    ~Cheese(); // Destructor
    // etc... other useful stuff follows
}

int main() {
    Cheese cheddar("Cabot Clothbound", 8.99);
    Cheese swiss("Jarlsberg", 4.99);

    whack swiss; 
    // fairly certain that "whack" is not a keyword,
    // but I am trying to make a point. Trash this instance!

    Cheese swiss("Gruyère",5.99);
    // re-instantiate swiss

    cout << "\n\n";
    return 0;
}

【问题讨论】:

  • “Cabot Clothbound”切达干酪怎么样 - 来自Cheddar
  • 在堆栈上声明的变量(类的实例)可以使用memset 将其内存设置为零(我猜这在某些情况下很有用......),但它的内存不能被释放,直到你声明它的函数范围结束(直到你的 main 结束)。如果你真的想留在堆栈上,你可以用复制构造函数覆盖你的变量内容,但是为什么你不使用堆和指针在这种情况下你可以delete你的实例?
  • @EdHeal:这不像香槟。这不是一个受保护的术语。这个名字指的是起源于切达干酪的食谱。有点像百万只“美国”灰松鼠,他们的父母和祖父母甚至从未听说过美国,更不用说把爪子放在那里了!
  • 我知道——不幸的是——但切达干酪的英文是最好的。香槟也是英国的另一项发明。又错过了:-(

标签: c++ class instance destructor kill


【解决方案1】:

在不知道用例或您要解决的实际问题的情况下(请阅读the XY problem,您的问题就是一个很好的例子),最简单的方法就是重新分配:

Cheese swiss("Jarlsberg", 4.99);
...
swiss = Cheese("Gruyère",5.99);

这当然可能需要您实现一个赋值运算符,但遵循rules of three or five,无论如何您都应该这样做(但如果您遵循the rule of zero,则不需要赋值运算符)。

如果您明确想要销毁当前的swiss 对象,您也可以使用指针:

Cheese* swiss = new Cheese("Jarlsberg", 4.99);
...
delete swiss;
swiss = new Cheese("Gruyère",5.99);

但是指针是你应该避免的一大堆蠕虫,在现代 C++ 中实际上并不需要太多。但是如果你想要多态,就需要指针(或引用)。然后你可以有一个指向实际实例的基类的指针,像虚函数这样的东西就会按预期工作。

此外,根据我们仍然一无所知的您的情况,您当然可以使用范围界定:

Cheese swiss("Jarlsberg", 4.99);
...
{
    Cheese swiss("Gruyère",5.99);
    // In here the swiss cheese is a Gruyère
    ...
}
// Out here the swiss cheese is a Jarlsberg

尽管像这样隐藏变量名有效,但您应该避免这是一个坏习惯,因为它会增加代码读者的困惑。另一方面,即使在使用范围时,也没有什么能阻止您使用所需的任何(有效)变量名,因此您可以将外部范围实例命名为 jarlsberg 和内部范围实例 gruyere,然后 gruyere 对象将在作用域的末尾被破坏,就像任何其他嵌套作用域变量会被破坏并“消失”一样。

【讨论】:

  • 名称阴影是一个坏主意,是的,但您可以更清楚地说明,使用大括号控制范围的一般技术是目前显示的三种技术中的 最佳技术。
  • 单独的作用域是最好的,但我会提倡顺序作用域,而不是嵌套作用域:{ Cheese swiss(1); } ... { Cheese swiss(2); }
  • 有用的建议,谢谢。我是否因此得出结论,如 OP 中所述,该功能在该语言中不存在?即:whack swiss;
【解决方案2】:

您可以使用作用域来定义类的另一个实例。

Cheese swiss("Toe", 3.14)

{
    Cheese swiss("Ear", 15.9);
}

作为一般规则,本地声明的实例在超出范围时会自行销毁。

如果你真的需要销毁奶酪,那么你需要改为动态分配它。

  Cheese *swiss = new Cheese("toe", 3);

   // do something with swiss.

   delete swiss;    // throw it away.

   swiss = new Cheese("Ear", 7);

   // do something with swiss.

   delete swiss;    // throw it away.

必须始终手动删除动态分配的内存。

【讨论】:

  • “作为一般规则” 阅读:绝对总是,没有例外。这就是超出范围的意思。
  • “必须始终手动删除动态分配的内存” 20 年前确实如此,但现在您应该使用智能指针,它基本上从不 正确的代码。
  • 我喜欢你的昵称。我同意你的看法。问题是操作是新的,否则他不会问这样的问题。答案试图指出他需要知道的两件事。答案已经足够复杂,无需添加智能指针或正确处理该代码所需的异常处理的概念。
  • 如果 OP 是新的,那么更重要的是不要误会他们,因为他们不会知道其中的区别。此外,智能指针是我们应该开始使用的简单方法;当 OP 有更多经验时,请留下诸如手动内存管理之类的高级主题。
  • 听起来你应该回答这个问题。
【解决方案3】:

在极少数情况下您需要这样做。但您可能会在创建抽象数据类型时遇到这种情况。

例如,如果您要创建变体类型,您可能需要设置对齐的数据类型,然后手动放置 new 和 delete。

typename std::aligned_union<0, FirstType, RestTypes...>::type m_buffer;

活过来:

new (&m_buffer) AssignType(forward<T>(x));

清除:

(HeldType*)(&m_buffer)->~HeldType();

但是,正如许多其他帖子中提到的那样。如果你编程正常,那么你就不用担心手动调用 dtors。如果它在堆栈上,那么它会为您清理干净。如果它在堆上,那么delete 将为您处理它。唯一需要这样做的地方是手动控制对象的生命周期,而这样做的主要原因是在实现抽象数据类型时。

【讨论】:

  • 除了“堆栈”/“堆”gubbins 之外的好答案
  • 有首选的命名法吗?
【解决方案4】:

一般规则 一旦超出范围,本地创建的实例将被自动删除/处理。 动态创建的Instances(Dynamic Memory Allocation)需要手动删除,如下:

删除实例名称;

  • 注意 : 不要在~cheese() (~destructor) 中包含delete instance_name 语句,否则会进入循环并崩溃

在上述情况下,您要手动删除实例,建议使用 DMA:

int main() {
    Cheese cheddar("Cabot Clothbound", 8.99);
    Cheese* swiss = new swiss("Jarlsberg", 4.99);

    delete swiss;


    Cheese swiss("Gruyère",5.99);
    // re-instantiate swiss

    cout << "\n\n";
    return 0;
}

【讨论】:

    【解决方案5】:

    C++ 的阴暗面提供了一种技术,可以完全按照您在原始帖子中的描述进行操作。我不知道你为什么要这样做——也许实现一个赋值运算符似乎很乏味。也许你有一种病态的愿望,想要对你的程序做一些不自然的事情。我可以向您保证,没有“正常”人会考虑使用我在下面透露的代码,至少在其他人观看时不会。那我为什么要把它放在这里呢?

    因为我是虐待狂。

    我呼吁社区 Wiki 的力量来保护我免受猛攻!

    #include <iostream>
    #include <string>
    #include <new>
    
    template <typename T, typename ...As>
    inline void Reconstruct(T &ojt, const As&... ctor_args) {
        // Explicitly call the destructor, to destroy the object
        // without doing anything to its memory allocation.
        ojt.~T();
    
        // Use Placement new to call a constructor.
        // Instead of allocating memory somewhere for a new object,
        // this specifies where the new object will be constructed --
        // given here as the location of the object's previous
        // incarnation.
        // Also pass any arguments to the constructor.  I forced
        // these arguments to be const references in order to
        // avoid extra copying, so "move" construction won't work
        // and steps might need to be taken to accommodate other,
        // more unusual constructors.
        new(&ojt) T(ctor_args...);
    }
    
    class Cheese {
        std::string brand;
        float       cost;
    public:
        Cheese() : cost(0) {}
        Cheese(std::string brand, float cost) : brand(brand), cost(cost) {}
        Cheese(const Cheese & rhs) = default;
        ~Cheese() = default;
        friend std::ostream& operator << (std::ostream& os, const Cheese& chz) {
            return os << "[brand:\"" << chz.brand << "\" cost:" << chz.cost << ']';
        }
    };
    
    int main() {
        Cheese cheese, fromage;
        std::cout << "cheese = " << cheese << '\n';
    
        Reconstruct(cheese, "Cabot Clothbound", 8.99f);
        std::cout << "cheese = " << cheese << '\n';
    
        Reconstruct(fromage, cheese);
        std::cout << "fromage = " << fromage << '\n';
    
        Reconstruct(cheese, "Jarlsberg", 4.99f);
        std::cout << "cheese = " << cheese << '\n';
    }
    

    【讨论】:

      【解决方案6】:

      如果由于某种原因你不能使用赋值运算符,你可以使用optional

      std::experimental::optional<Cheese> swiss(std::experimental::in_place, "Jarlsberg", 4.99);
      
      swiss = std::experimental::nullopt; // Calls Cheese::~Cheese internally
      
      // re-instantiate swiss
      swiss.emplace("Gruyère",5.99);
      

      只要您不存储可选项,您就可以依靠编译器优化额外的内部布尔值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多