【问题标题】:Calling a constructor to re-initialize object调用构造函数重新初始化对象
【发布时间】:2010-01-29 23:53:32
【问题描述】:

是否可以使用其构造函数重新初始化类的对象?

【问题讨论】:

  • 重新初始化到底是什么意思?
  • 在变量第一次创建时赋予它们相同的值。
  • Neil 的解决方案是“最好的”,尽管您的设计需要修复。
  • 真的需要重新初始化对象吗?难道你不能用新值创建一个相同类型的新对象并删除旧对象吗?

标签: c++ constructor


【解决方案1】:

有点。给定一个 A 类:

A a;
...
a = A();   

最后一条语句不是初始化,它是赋值,但它可能会做你想做的事。

【讨论】:

  • +1 当然这也需要编写一个完整、正确的赋值运算符。
  • @Greg 这需要有一个正确的赋值运算符,不一定要写一个 - 默认值通常是完全可以的。
  • 如果a 是使用new 关键字实例化的,这会导致资源泄漏吗?
  • @davidhood2 在这种情况下,您无法使用 new 关键字进行实例化,那么您将创建 A* 而不是 A。如果您分配给 A* 并且您使用 new,您应该知道您在哪里正在删除它以避免资源泄漏。与其自己编写删除操作,不如使用智能指针更安全。
  • 奇怪的是,当我这样做时,我得到一个use of deleted function 错误。
【解决方案2】:

真的吗?是的,通过使用新的展示位置。但首先你必须破坏之前构造的对象。

SomeClass object(1, 2, 3);
...
object.~SomeClass(); // destruct
new(&object) SomeClass(4, 5, 6); // reconstruct
...
// Final destruction will be done implicitly

尽管如此,它的价值并没有超出纯粹的理论范围。不要在实践中这样做。整件事的丑陋无法形容。

【讨论】:

  • 你能解释一下为什么你认为这是“难以描述的丑陋”吗?在我看来,它在避免代码重复和由于将clear 方法与构造函数和析构函数分开而产生的错误方面具有合法用途。
  • 这个“丑陋”的原因在于,在object 上调用析构函数后,您有一个未初始化的堆栈变量。在此之后尝试对object 做任何事情都是不安全的。甚至没有办法检测到该对象后来已被破坏。但一旦重建,它又是有效的。我想说,只要这两行是背靠背的,这是安全的。
  • 老实说,我可能会将它放在某种 vardic 模板函数甚至只是一个宏中,以明确发生了什么并防止代码被意外插入两个语句之间。
  • 对我非常有用,因为我正在努力实现一个内存池......谢谢!不过,我同意……除非您对如何处理分配和解除分配负责,否则这真是个坏主意。 :)
  • @ElProfesor 在我的示例中,对象是在本地(或静态)声明的,这意味着编译器将像往常一样在对象的生命周期结束时执行其销毁。 IE。编译器会自动/隐式销毁它。
【解决方案3】:

这是可能的,尽管这是一个非常糟糕的主意。原因是如果不调用现有对象的析构函数,就会泄漏资源。

有了这个重要的警告,如果你坚持这样做,你可以使用placement new。

// Construct the class
CLASS cl(args);

// And reconstruct it...
new (&cl) CLASS(args);

【讨论】:

  • 你应该只因为向某人推荐它而得到 -1 :P,但因为 hack 得到 +1 -- 不知道它,所以总共 0
  • 正如我在 Jared 的回答中所说的那样,在放置新位置之前可以cl->~CLASS();。我不确定这是否已定义,似乎合法。
  • @Alexander:就答案而言,它是邪恶的和未定义的。
  • 不,解决方案不正确。务必记住首先销毁“旧”对象。
  • 如果析构函数不做任何你依赖的事情,C++ 明确允许你在创建新对象之前省略调用它。但调用它并没有什么坏处。在“最坏”的情况下,它是隐式声明的,编译器将优化调用。在最好的情况下,析构函数将释放仍被占用的内存。但事实上,我认为分配是要走的路。
【解决方案4】:

在 C++11 中,您可以这样做:

#include <type_traits>

template <class T, typename... Args>
void Reconstruct(T& x, Args&&... args)
{
    static_assert(!std::has_virtual_destructor<T>::value, "Unsafe"); 
    x.~T();
    new (&x) T(std::forward<Args>(args)...);
}

这允许您使用Reconstruct 将任意构造函数参数传递给任何对象。这可以避免必须维护一堆 Clear 方法,以及如果在某些时候对象更改并且 Clear 方法不再匹配构造函数时很容易被忽视的错误。

上述方法在大多数情况下都可以正常工作,但如果引用是对具有虚拟析构函数的派生对象中的基类的引用,则会严重失败。出于这个原因,上述实现禁止使用具有虚拟析构函数的对象。

【讨论】:

    【解决方案5】:

    简答:

    没有。如果您的对象的部分预期行为要被初始化多次,那么实现这一点的最佳方法是通过可访问的初始化方法。您的类的构造函数可以简单地遵循此方法。

    class C1 {
    public:
      C1(int p1, int p2) {
        Init(p1,p2);
      }
      void Init(int p1, int p2) { ... }
    };
    

    Nitpicker 角落:

    在创建对象后,在 C++ 中调用构造函数是否有一些非常邪恶的方法?几乎可以肯定,这毕竟是 C++。但它从根本上是邪恶的,它的行为几乎可以肯定没有由标准定义,应该避免。

    【讨论】:

    • 你可以这样做:T x; x-&gt;~T(); new (&amp;x) T(); 我不确定这是如何定义的,它看起来几乎没问题。 (在法律上还可以,在这完全很棒的代码中不行。)
    【解决方案6】:

    不,构造函数仅在对象第一次创建时被调用。写一个新方法来代替它。

    编辑

    我不会承认新的安置,因为我不想让宠物猛禽上班。

    See this comic,不过想想手头的话题……

    【讨论】:

      【解决方案7】:

      是的,您可以作弊并使用新的展示位置。
      注意:我不建议这样做:

      #include <new>
      
      reInitAnA(A& value)
      {
          value.~A();            // destroy the old one first.
          new (&value) A();      // Call the constructor 
                                 // uses placement new to construct the new object
                                 // in the old values location.
      }
      

      【讨论】:

      • 在这种情况下 new 不会失败(就内存分配而言),因为没有分配内存。构造函数可能会失败并抛出异常,就像您期望的那样。
      • 可能 Jagannath 的意思是,如果抛出异常,并且作为参数传递的对象是 e.g.属于调用者的自动变量,或由智能指针持有的动态分配的对象。什么时候调用两次析构函数是有效的,如何写代码保证有效性等等?
      【解决方案8】:

      我通常用现代 C++ 编写以下代码:

      SomeClass a;
      ...
      a = decltype(a)();
      

      这可能不是最有效的方法,因为它有效地构造了另一个a 相同类型的对象并将其分配给a,但它在大多数情况下都有效,您不必记住类型a,如果类型发生变化,它会适应。

      【讨论】:

        【解决方案9】:

        可能不是你想的那样,但由于你没有提到它的用途,我想一个答案是你会通过控制范围和程序流程来做到这一点。

        例如,你不会写这样的游戏:

        initialize player
        code for level 1
        ...
        reinitialize player
        code for level 2
        ...
        etc
        

        相反,你会努力:

        void play_level(level_number, level_data) {
            Player player; //gets "re-initialized" at the beginning of each level using constructor
            //code for level
        }
        
        void game() {
            level_number = 1;
            while (some_condition) {
                play_level(level_number, level_data);
                ++level_number;
            }
         }
        

        (表达想法的非常粗略的大纲,并不意味着可以远程编译。)

        【讨论】:

          【解决方案10】:

          与其像上面的一些答案所建议的那样进行破坏和重新初始化,不如像下面这样进行分配。下面的代码是异常安全的。

              T& reinitialize(int x, int y)
              {
                  T other(x, y);
                  Swap(other); // this can't throw.
                  return *this;
              }
          

          【讨论】:

            【解决方案11】:

            虽然大多数答案是分两步重新初始化对象;首先,创建一个初始对象,然后创建另一个对象并使用 placement new 将其与第一个对象交换,此答案涵盖了您首先创建指向空对象的指针然后分配并构造它的情况:

            class c *c_instance; // Pointer to class c
            c_instance = new c(arg1, ..., argn) // Allocate memory & call the proper constructor 
            // Use the instance e.g. c->data
            delete c_instance; // Deallocate memory & call the destructor 
            

            【讨论】:

              【解决方案12】:

              是的,这是可能的。 如果您创建一个返回新对象的方法。

              #include "iostream"
              class a // initialize class
              a getNewA(a object){// Create function to return new a object
              a new_object(/*Enter parameters for constructor method*/);
              return new_object;
              }
              

              【讨论】:

                【解决方案13】:

                如果你真的必须这样做,我强烈建议为此创建一个重置方法:

                class A
                {
                 ...
                  public:
                    reset() { *this= A() };
                }
                

                以上要求 A 是可复制和可移动的。 那是因为初始优化版本将从临时复制。但是复制省略可能会删除此步骤。

                A::reset() 的行为总是很好定义的。它将有效 A 实例中的现有数据替换为新实例中的数据。当然,如果您还希望重新初始化其数据,则 A 的任何子类仍需要定义其自己的版本。但是,不这样做本身并不会调用未定义或未指定的行为。它只是意味着只有 A 为其成员使用的任何内存都会被重置。即使涉及虚拟功能和/或虚拟继承也不会改变这一点。虽然使用这些东西通常需要注意。

                上述方法不会删除原始指针,因此需要将它们放入 std::shared_ptr 或类似结构中,以便在不再需要时自毁。

                【讨论】:

                  猜你喜欢
                  • 2011-10-15
                  • 1970-01-01
                  • 2012-01-02
                  • 1970-01-01
                  • 2012-03-05
                  • 2013-04-01
                  • 2023-03-14
                  • 1970-01-01
                  • 2021-11-06
                  相关资源
                  最近更新 更多