繁体   English   中英

C ++ - 构造类中的对象

[英]C++ - construction of an object inside a class

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

class Foo
{
    //stuff
};

class Bar
{
    Foo foo;
};

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

谢谢。

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

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

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

    Foo foo;
};

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

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

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

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

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

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

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

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

这当然是假设你在代码中添加一个Foo :: Foo(int):)

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

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

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

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

如果Foo具有默认ctor,则在创建Bar类型的对象时,类型为Foo的对象将使用默认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;
}

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

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

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

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

    Foo foo;
};

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

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

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

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

Foo foo(somearg)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM