在C++中,有没有一种方法可以延迟初始化成员变量(一个类)?

14

我来自Java背景。我有以下程序。

#include <string>
#include <iostream>

class First {
    public:
    First(int someVal): a(someVal) {

    }
    int a;
};

class Second {
    public:
    First first;
    Second()  {   // The other option would be to add default value as ": first(0)"
        first = First(123);

    }
};

int main()
{
    Second second;
    std::cout << "hello" << second.first.a << std::endl;
}

Second 类中,我希望变量 first 保持未初始化状态,直到我在 Second() 构造函数中明确初始化它。有没有办法做到这一点?或者我只能选择以下两个选项:

  1. 提供一个无参构造函数。
  2. 用某个默认值初始化它,然后重新分配所需的值。

我不能在初始化列表中使用正确的值初始化 first,因为该值是在一些操作之后获得的。因此,first 的实际所需值仅在 Second() 构造函数中可用。


3
一旦你实例化一个 Secondfirst 就会被初始化。这是无法避免的。你只能在以后的阶段改变它的值。 - juanchopanza
1
使用指针。在Java中,一切都是指针,这可能会让你感到困惑。 - Peter - Reinstate Monica
@juanchopanza,所以你建议采用这种方式: Second() : first(0) { first = First(123); } - linuxeasy
5
可以通过计算并将其用于参数列表中的 first 的初始化来实现。使用一个函数。 - Mooing Duck
1
因此,请使用适当的智能指针类型,即unique_ptrshared_ptr,具体取决于需要做什么样的析构函数、赋值和复制操作。 - Phil Miller
显示剩余3条评论
6个回答

12

我的建议是使用一个函数:

private: static int calculate_first(int input) {return input*5;}
explicit Second(int input) : first(calculate_first(input)) {}

基类将按照它们在类继承列表中声明的顺序进行初始化,然后成员将按照它们在类中列出的顺序进行初始化,所以如果非静态成员变量和基类已经被初始化,计算可以依赖它们。


或者:

默认构造函数,然后重新分配:

explicit Second(int input) { first = input*5; }

虚拟值,然后重新赋值:

explicit Second(int input) : first(0) { first = input*5; }

使用boost :: optional(或C++17中的std :: optional):

boost::optional<First> first;
explicit Second(int input) { first = input*5; }

使用堆:

std::unique_ptr<First> first;
explicit Second(int input) { first.reset(new First(input*5));}
Second(const Second& r) first(new First(*(r->first))) {}
Second& operator=(const Second& r) {first.reset(new First(*(r->first)));}

Placement new:(在指定内存地址上)调用对象的构造函数,返回此内存地址。

This is tricky and not suggested 
and worse in every way than boost::optional
So sample deliberately missing.
But it is an option.

2
@linuxeasy: 基类将按照它们在类继承列表中声明的顺序进行初始化,然后成员将按照它们在类中列出的顺序进行初始化,因此如果它们已经被初始化,计算可以依赖于非静态成员变量和基类。 - Mooing Duck
1
实际上,除了前两个之外,其他都应该避免使用。另一方面……我偶尔发现为像他的“First”这样的东西提供一个“简化”的构造函数很有用,它需要一个显式的虚拟参数来告诉构造函数什么也不做(或者尽可能少地做一些后续分配的工作)。 - James Kanze
可悲的是,现在已经是C++20了,但我们仍然没有更好的编写构造函数的方法。 - RnMss
@RnMss:它永远不会被解决。根本的问题在于组合如何工作。这些是理论上的解决方案。我可能漏掉了什么,但这永远不会变得“更好”。Java的解决方案只是堆变量的语法糖。 - Mooing Duck
然而不知怎么的,到目前为止它在每种语言的每个程序中都能正常工作... - Mooing Duck
显示剩余2条评论

4

在成员初始化列表中初始化first

可以通过使用辅助函数进行计算并使用转发构造函数来简化操作:

class Second {
public:
    Second() : Second(helper_function()) {}

private:
    Second(int calc): first(calc) {}
    static int helper_function() { return ...; }

    First first;
};

关于此处提到的静态辅助函数,如果计算依赖于非静态成员变量或基类的成员变量,那么这种方法是行不通的,对吗? - linuxeasy
@linuxeasy 不会的。如果你小心地只引用在“first”之前定义的成员,你可以将其设置为非静态成员函数。 - ecatmur

3

这句话是问题的核心:

我无法在初始化程序列表中使用正确的值进行初始化,因为该值是在一些操作之后获得的。

您应该知道,在Java中,您想要做的事情也不是完美的编程风格。留下一些默认值,然后在一些计算完成后再分配它有效地防止其成为final,因此阻止了类的不可变性。

无论如何,您的目标必须是将这些计算直接推入成员的初始化中,使用私有辅助函数(可以是静态的):

class Second {
private:
    First first;

    static int getInitializationData()
    {
        // complicated calculations go here...
        return result_of_calculations;
    }
public:
    Second() : first(getInitializationData()) {}
};

我认为,其他任何方法都只是权宜之计,从长远来看会使你的生活变得更加复杂。


2
您可以按照评论中所说的做,或者您可以先创建一个指向First的指针,并在需要时分配内存,尽管我不建议使用这种方式。

1

将对象生命周期分离的一种方法是使用堆,将first设为指针并在任何时候进行初始化:

class Second {
public:
    First* first;
    Second()  { 
        first = new First(123);

    }
};

当然,你可能希望使用某种智能指针而不是一个原始指针。

我认为在这种情况下不需要智能指针,特别是因为它在类中,他可以在dtor()中删除它。 - DTSCode
1
@DTSCode 一个 std::unique_ptr 将会完全执行在析构函数中的 delete 操作,而无需在析构函数中编写额外的代码。那么不这样做的好处在哪里呢? - Paul Evans
@DTSCode:仅在析构函数中删除它将给类带来无效的复制语义。您需要更多工作或智能指针(或其他答案中更好的建议之一)来解决这个问题。 - Mike Seymour

0

如果您不编写代码来显式初始化成员变量,则默认初始化程序将用于初始化它。

草案C++标准关于基类和成员变量的初始化如下:

12.6 初始化[class.init]

1 当没有为(可能是cv限定的)类类型对象(或其数组)指定初始化程序,或者初始化程序具有形式()时,对象将按8.5中指定的方式初始化。

12.6.1 显式初始化[class.expl.init]

1 可以使用带括号的表达式列表初始化类类型对象,其中表达式列表被解释为调用构造函数以初始化对象的参数列表。或者,可以使用=初始化形式指定单个赋值表达式作为初始化程序。直接初始化语义或复制初始化语义适用;请参见8.5。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接