C ++ - 在类中构造对象

Posted

技术标签:

【中文标题】C ++ - 在类中构造对象【英文标题】:C++ - construction of an object inside a class 【发布时间】:2010-10-25 09:20:49 【问题描述】:

我对 C++ 还很陌生,对此我不确定。看看下面的例子,它总结了我当前的问题。

class Foo

    //stuff
;

class Bar

    Foo foo;
;

所以 Bar 包含一个完整的 Foo 对象,而不仅仅是一个引用或指针。该对象是否由其默认构造函数初始化?我是否需要显式调用它的构造函数,如果需要,如何以及在哪里?

谢谢。

【问题讨论】:

我对 C++ 很熟悉,但我仍然经常有一些疑问。此外,大多数答案都明确指出将调用 Foo 默认构造函数,而事实是它取决于 Foo 本身的定义。它是用户提供的还是隐式的默认构造函数?它有任何私有成员属性吗? C++中的初始化并不简单。 很有趣的是@xtofl 要求发布者删除“我熟悉 C++”……也许大多数人对 C++ 并不“熟悉”,而几乎所有的答案都是错误的。事实上初始化很困难,一些回答的人已经证明了他们的 C++ 知识@JaredPar、@dirkgently、@David Thornley,但都失败了。 【参考方案1】:

它将由其默认构造函数初始化。如果你想使用不同的构造函数,你可能会有这样的东西:

class Foo

    public: 
    Foo(int val)  
    //stuff
;

class Bar

    public:
    Bar() : foo(2)  

    Foo foo;
;

【讨论】:

每个类的结束 后加一个分号 (;)。我责怪 Java。 :-) uservoice 请求:markdown 框上的代码完成和错误突出显示。 第一句错了。不保证 Foo 的初始化。这取决于如何为给定的 Bar 定义 Foo。在页面末尾阅读我的答案:***.com/questions/849812/… 毫无疑问 Foo 是如何定义的,它在这篇文章的开头已经定义了。显然,我不打算将该声明推广到 Foo 的任何潜在实现。 这里的重点是答案只适用于可能性的子集。 Bar 没有用户提供的构造函数这一事实可能表明 Foo 也没有,在这种情况下答案是错误的。在不知道事实的情况下回答问题就像试图回答“我必须在双向街道的左侧还是右侧开车?”。这取决于未知因素:“你是在美国还是在英国开车?”在“右侧”提供不知情的答案,没有警告可能会导致事故。【参考方案2】:

在 C++ 中,构造是一个相当困难的话题。简单的答案是视情况而定。 Foo 是否初始化取决于 Foo 本身的定义。关于第二个问题:如何让 Bar 初始化 Foo:初始化列表就是答案。

虽然普遍的共识是 Foo 将由隐式默认构造函数(编译器生成)默认初始化,但这不需要成立。

如果 Foo 没有用户定义的默认构造函数,则 Foo 将未初始化。更准确地说:Bar 或 Foo 的每个成员缺少用户定义的默认构造函数将被编译器生成的 Bar 的默认构造函数初始化

class Foo 
   int x;
public:
   void dump()  std::cout << x << std::endl; 
   void set()  x = 5; 
;
class Bar 
   Foo x;
public:
   void dump()  x.dump(); 
   void set()  x.set();  
;
class Bar2

   Foo x;
public:
   Bar2() : Foo() 
   void dump()  x.dump(); 
   void set()  x.set(); 
;
template <typename T>
void test_internal() 
   T x;
   x.dump();
   x.set();
   x.dump();

template <typename T>
void test() 
   test_internal<T>();
   test_internal<T>();

int main()

   test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0
   test<Bar>(); // prints ??, 5, 5, 5
   test<Bar2>(); // prints 0, 5, 0, 5

现在,如果 Foo 有一个用户定义的构造函数,那么无论 Bar 是否有用户初始化的构造函数,它都会被初始化。如果 Bar 有一个用户定义的构造函数显式调用 Foo 的(可能是隐式定义的)构造函数,那么 Foo 实际上将被初始化。如果 Bar 的初始化列表没有调用 Foo 构造函数,则相当于 Bar 没有用户定义的构造函数。

测试代码可能需要一些解释。我们对编译器是否在没有用户代码实际调用构造函数的情况下初始化变量感兴趣。我们要测试对象是否被初始化。现在,如果我们只是在函数中创建一个对象,它可能会碰巧碰到一个未被触及且已经包含零的内存位置。我们想区分运气和成功,所以我们在函数中定义了一个变量并调用了两次函数。在第一次运行时,它将打印内存内容并强制更改。在对函数的第二次调用中,由于堆栈跟踪相同,因此变量将被保存在完全相同的内存位置。如果它被初始化,它将被设置为 0,否则它将保持与旧变量完全相同的值。

在每个测试运行中,打印的第一个值是初始化值(如果它实际上已被初始化)或该内存位置中的值,在某些情况下恰好为 0。第二个值只是一个测试令牌表示手动更改后内存位置的值。第三个值来自函数的第二次运行。如果变量正在初始化,它将回退到 0。如果对象未初始化,则其内存将保留旧内容。

【讨论】:

你知道为什么当你运行 test() 输出是 ??, 5, 5, 5 而不是 ??,5,??,5 不是 Foo 对象 ( x) 当 test_internal 返回时超出范围?同样对于 test() ? @user1084113:因为它是未定义的行为,你可以得到任何一种,但是测试滥用了编译器/架构通常如何使用堆栈的知识。基本上,当输入一个函数时,它会为其内部变量抓取堆栈的一部分,然后在函数结束时释放(取决于调用约定,它可能是调用者或更新堆栈指针的函数)。该函数的第二次调用使用相同的内存块存储局部变量,按相同的顺序放置。 x 未初始化,具有在函数上次运行时分配的值。【参考方案3】:

C++ 编译器将为每个类生成四个函数(如果可以,如果您不提供它们):默认构造函数、复制构造函数、赋值运算符和析构函数。在 C++ 标准(第 12 章,“特殊函数”)中,这些被称为“隐式声明”和“隐式定义”。他们将拥有公共访问权限。

不要将构造函数中的“隐式定义”与“默认”混淆。默认构造函数是可以在没有任何参数的情况下调用的构造函数,如果有的话。如果您不提供构造函数,则将隐式定义默认构造函数。它将为每个基类和数据成员使用默认构造函数。

所以,发生的事情是类 Foo 有一个隐式定义的默认构造函数,而 Bar(它似乎没有用户定义的构造函数)使用其隐式定义的默认构造函数,它调用 Foo 的默认构造函数。

如果您确实想为 Bar 编写构造函数,您可以在其初始化列表中提及 foo,但由于您使用的是默认构造函数,因此您实际上不必指定它。

请记住,如果您确实为 Foo 编写了构造函数,编译器将不会自动生成默认构造函数,因此如果需要,您必须指定一个。因此,如果你将Foo(int n); 之类的东西放入Foo 的定义中,并且没有显式地编写默认构造函数(Foo();Foo(int n = 0);),你就不可能有现在形式的 Bar,因为它不能使用 Foo 的默认构造函数。在这种情况下,您必须有一个像 Bar(int n = 0): foo(n) 这样的构造函数,让 Bar 构造函数初始化 Foo。 (请注意,Bar(int n = 0) foo = n; 或类似的东西不起作用,因为 Bar 构造函数会首先尝试初始化 foo,这会失败。)

【讨论】:

Bar 中隐式定义的构造函数不会调用 Foo 中隐式定义的构造函数。如果 Foo 有一个用户定义的构造函数,那么 Bar 隐式定义的构造函数将(这次是)调用 Foo 中的用户定义的构造函数。这是迄今为止最完整的答案:+1 确定吗?根据 12.6.2(4),如果在初始化列表中未提及,则使用其默认构造函数初始化非静态数据成员,并且在 8.5(5) 中调用默认构造函数,除非它是 POD(普通旧数据,不方便定义在 Standard) 类型中,在这种情况下它被初始化为零。所以,我想问题是 Foo 是否是 POD 类型,我们看不到。如果是POD类型,则全部初始化为零;如果它具有将其标记为非 POD 的事物之一,则它是默认初始化的,因此会调用隐式定义的默认构造函数。【参考方案4】:

如果您没有在 Bar 的构造函数中显式调用 foo 的构造函数,那么将使用默认的构造函数。您可以通过显式调用构造函数来控制它

Bar::Bar() : foo(42) 

这当然是假设您在代码中添加了 Foo::Foo(int) :)

【讨论】:

我看到你们中的大多数人在示例中都提出了“42”。这是哪里来的? 这是对 H2G2 的参考——对生命、宇宙和一切终极问题的答案。阅读:en.wikipedia.org/wiki/…。更好地阅读道格拉斯·亚当斯。 不正确:如果Foo的默认构造函数是用户定义的,它将被调用。如果用户没有提供,编译器生成的隐式默认构造函数将不会被调用。可以强制编译器在初始化列表中调用它 是否可以在不使用初始化列表的情况下初始化对象?如题,可以在构造函数的 中初始化对象吗?【参考方案5】:

所以 Bar 包含一个完整的 Foo 对象,而不仅仅是一个引用或指针。这个对象是由它的默认构造函数初始化的吗?

如果Foo 具有默认ctor,则Foo 类型的对象将在您创建Bar 类型的对象时使用默认ctor。否则,您需要自己调用Foo ctor,否则您的Bar 的ctor 会让您的编译器大声抱怨。

例如:

class Foo 
public:
 Foo(double x) 
;

class Bar  
 Foo x;
;

int main() 
 Bar b;

上面会有编译器抱怨:

“在构造函数'Bar::Bar()'中:第5行:错误:没有匹配函数调用'Foo::Foo()'

我是否需要显式调用它的构造函数,如果需要,如何以及在哪里?

将上面的例子修改如下:

class Foo 
 public:
  Foo(double x)  // non-trivial ctor
;

class Bar       
 Foo x;
public:
  Bar() : x(42.0)  // non-default ctor, so public access specifier required
;

int main() 
 Bar b;

【讨论】:

如果 Foo 没有定义任何构造函数(除了可能的复制构造函数),那么编译器根本不会抱怨,但也不会调用隐式定义的 Foo 构造函数。该对象将未初始化。 这就是我的默认ctor的意思。 是否可以在不使用初始化列表的情况下初始化对象?如题,可以在构造函数的 中初始化对象吗?【参考方案6】:

除非您另外指定,否则 foo 会使用其默认构造函数进行初始化。如果你想使用其他构造函数,你需要在 Bar 的初始化列表中这样做:

Bar::Bar( int baz ) : foo( baz )

    // Rest of the code for Bar::Bar( int ) goes here...

【讨论】:

如果 Foo 有一个用户定义的默认构造函数,那么它将被调用。对于Foo中隐式定义的默认构造函数,Bar的隐式定义的构造函数不会调用。 是否可以在不使用初始化列表的情况下初始化对象?如题,可以在构造函数的 中初始化对象吗? @JustinLiang,不,一旦构造函数的主体被执行,属于将被初始化的类的任何对象都已被初始化。如果对象没有默认构造函数,则需要调用初始化列表中的构造函数。但是,您可以使用赋值来更改类中的对象和数据,但请注意,这样做效率较低,因为对象正在初始化,然后被赋值覆盖。【参考方案7】:

完整的对象。不,它是在 Bar 的默认构造函数中默认构造的。

现在,如果 Foo 有一个只接受一个 int 的构造函数。你需要 Bar 中的构造函数来调用 Foo 的构造函数,并说出它是什么:

class Foo 
public:
    Foo(int x)  .... 
;

class Bar 
public:
    Bar() : foo(42) 

    Foo foo;
;

但如果 Foo 有一个默认构造函数 Foo(),编译器会自动生成 Bar 的构造函数,这会调用 Foo 的默认构造函数(即 Foo())

【讨论】:

我看到你们中的大多数人在您的示例中都提出了“42”。这是哪里来的? 糟糕。固定的。在思考/谈论其他概念时,我倾向于忽略这一点。 @ichiban:阅读 Douglas Adams 的《银河系漫游指南》。这是一部经典的非常有趣的小说,大多数技术人员都喜欢。 (虽然 5 本书系列中的后一个实例不如第一个实例好..) @dirkgently:编译器生成的(“隐式声明”)函数(默认构造函数、复制构造函数、赋值运算符、析构函数)默认为“内联公共”。除非另有说明,否则显式声明的类成员是私有的。 再次与其他答案相同的评论:如果 Foo 没有用户定义的默认构造函数,除非您从 Bar 的构造函数(初始化列表)显式调用 Foo 默认构造函数,否则 Foo 将未初始化。 【参考方案8】:

构造函数用于设置对象的初始状态。在继承层次结构的情况下,基类对象将按照继承层次结构(OO术语中的IS-A关系)的顺序构造,然后是派生类对象。

类似地,当一个对象嵌入另一个对象(OO 术语中的 HAS-A 关系或包含)时,嵌入对象的构造函数按其声明的顺序调用。

编译器解析类 B 的所有成员,然后为每个方法生成代码。此时,它知道所有成员及其顺序,除非另有说明,否则它将使用默认构造函数按顺序构造成员。所以 foo 是在没有显式调用其默认构造函数的情况下构造的。您需要做的只是创建 Bar 的对象。

【讨论】:

【参考方案9】:

您不需要在 C++ 中显式调用默认构造函数,它会为您调用。如果你想调用不同的构造函数,你可以这样做:

Foo foo(somearg)

【讨论】:

如果 Foo 有一个用户定义的默认构造函数,那么它将被调用。对于Foo中隐式定义的默认构造函数,不会在Bar的隐式定义构造函数中进行调用

以上是关于C ++ - 在类中构造对象的主要内容,如果未能解决你的问题,请参考以下文章

c ++:使用模板在类中定义可变长度数组

C++为什么不能在类中定义该类的对象

JavaSE知识-08(面向对象_继承&方法&final)

Java:面向对象--继承

c++(在类中)执行buf=new char[i];delete []buf; 为啥没有调用构造和析构函数?

自描述C++部分面试题集