C ++ - 构造类中的对象

时间:2009-05-11 20:02:47

标签: c++ constructor oop

我对C ++很新,我不确定这个。看看下面的例子总结了我当前的问题。

class Foo
{
    //stuff
};

class Bar
{
    Foo foo;
};

因此Bar会保留一个完整的Foo对象,而不仅仅是一个引用或指针。此对象是否由其默认构造函数初始化?我是否需要显式调用其构造函数,如果是,如何以及在何处?

感谢。

9 个答案:

答案 0 :(得分:22)

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

class Foo
{
    public: 
    Foo(int val) { }
    //stuff
};

class Bar
{
    public:
    Bar() : foo(2) { }

    Foo foo;
};

答案 1 :(得分:16)

构建在C ++中是一个相当难的主题。简单的答案是它取决于。是否初始化Foo取决于Foo本身的定义。关于第二个问题:如何使Bar初始化Foo:初始化列表就是答案。

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

如果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.如果对象未初始化,则其内存将保留旧内容。

答案 2 :(得分:6)

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,这会失败。)

答案 3 :(得分:3)

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

来控制它
Bar::Bar() : foo(42) {}

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

答案 4 :(得分:1)

  

因此Bar会保留一个完整的Foo对象,而不仅仅是一个引用或指针。此对象是否由其默认构造函数初始化?

如果Foo具有默认ctor,则在创建Foo类型的对象时,类型为Bar的对象将使用默认ctor。否则,您需要自己致电Foo ctor,否则您的Bar的ctor将使您的编译器产生抱怨。

E.g:

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;
}

答案 5 :(得分:1)

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

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

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

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

    Foo foo;
};

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

答案 6 :(得分:1)

除非另行指定,否则使用其默认构造函数初始化foo。如果要使用其他构造函数,则需要在Bar的初始化列表中执行此操作:

Bar::Bar( int baz ) : foo( baz )
{
    // Rest of the code for Bar::Bar( int ) goes here...
}

答案 7 :(得分:1)

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

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

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

答案 8 :(得分:0)

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

Foo foo(somearg)