【问题标题】:Inherited class initializing a custom made class using non-default constructor使用非默认构造函数初始化自定义类的继承类
【发布时间】:2012-03-29 15:32:36
【问题描述】:

所以我到处搜索,似乎找不到这个特定问题的答案。我正在使用带有 cygwin 和 gcc 3.4.4 cygming special 的 winXP。

问题: 我有一个类作为接口工作,其中包含一些抽象方法和受保护的变量,这些变量应该存在于从该类继承的每个类中。现在我还有另一个类是这个接口的成员变量。

class Bar {
private:
    int y;
public:
    Bar(int why);
};

Bar::Bar(int why) : y(why) {}

class Foo {
protected:
    Bar b;
public:
    Foo(int x);
    virtual void print_base();
};

Foo::Foo(int x) : b(x+3)  // Have to use initializer list here.
{
    //this->b(x+3); // doesn't work
}

class DerFoo : public Foo {
protected:
    Bar db;
public:
    DerFoo(int x);
};

DerFoo::DerFoo(int x) : Foo(x), 
    db(x+3) // (Bar)(int) being called, works fine
    // db(4.0, 30) // no matching function for call to Bar::Bar(double, int)
    // note: candidates are Bar::Bar(const Bar&), Bar::Bar(int)
    // b(x-3) // doesn't work class DerFoo does not have any field named 'b'
{
    //this->b(x - 3); //  Doesn't work, error no match for call to (Bar)(int)
    //this->db(x + 3); // Doesn't work, error no match for call to (Bar)(int)
}

所以你看到的问题是在派生的 foo 类内部,DerFoo 如何初始化 b。我已经尝试过成员初始化方法,但是编译器没有意识到受保护的变量。那么由于我不知道的一些奇怪的原因,它在这个类中找不到构造函数。即使包含对受保护成员变量(非继承)的构造函数的“错误”调用,它也会建议构造函数的正确版本。

我仍然不知道如何做到这一点。非常感谢任何帮助。

【问题讨论】:

  • 我很困惑 - 通过一些小的调整,这在 ideone (ideone.com/2cLUa) 上编译得很好。您遇到了什么错误,在哪里?
  • 好的,我想我知道你在哪里弄糊涂了。在您发布的代码中,Foo::b 成员变量 is 在您调用 Foo(x) 时被初始化。您可以向Bar 构造函数添加调试消息,您会看到它被初始化了两次——一次用于b,一次用于db。一旦b被初始化,你就不能重新初始化,你必须使用=(赋值运算符)给它分配一个新的值。
  • 对。感谢大家的回答,非常有见地和乐于助人。由于有人认为这是“非常低效的”,因此我的真实代码在接口级别有很多变量。这对我来说可能是一个糟糕的设计,但我认为这对这个项目并不重要。所以我想我会选择这个选择,Foo(int b, int c, double d)... 用于我的所有成员变量,然后做DerFoo(int derA, derX) : Foo (b,c,d) { ...
  • @tomasgudm:还有另一种方法可以让您的代码更高效:在您的情况下,您将数据从调用代码传递到 DerFoos 构造函数到 Foos 构造函数到 @987654334 @s 构造函数,然后使用它来构造实际对象。这意味着您的数据在使用之前会经过三层。如果您直接在DerFoo 构造函数中创建Bar 对象,将其传递给Foo 构造函数,然后将std::move it (C++11) 传递给b 字段,您将有效降低分层和参数数量,同时保持高效。

标签: c++ inheritance member-variables


【解决方案1】:

声明一个变量后,你必须设置它,否则你会像调用函数一样调用它。

this->b = Bar(x+3);

首选方法是使用初始化列表来避免不必要的Bar 副本。但是,如果您这样做,则需要在构造函数之外设置 b 上面的示例是如何设置的。

【讨论】:

  • 谢谢,我以为我已经尝试过了,但显然我的代码中的其他错误(我现在已经修复)阻止了它当时的工作。我简直要疯了,谢谢。
  • 这会构造两次Bar,这在所描述的情况下绝对是低效的。
  • 这至少构造了三次Bar。一次用于b,一次用于db,一次用于分配期间。
  • 肯定会发生 2 个构造,如果编译器不执行复制省略(或不是 C++11),则可能是第 3 个构造,但基于 OP 的代码,这是不使用初始化程序的唯一选择列出除非Bar 提供了一套y 方法。
  • @Rob:是的,我们谈论的是不同的台词。我在谈论基类构造函数,并错过了派生类构造函数中还有另一个doesn't work。在基类中将有两个 Bars,它们是构造的,而在派生类中是三个。
【解决方案2】:

DerFoo 的构造函数不能(也不能)初始化b,这是Foo 的工作。 DerFoo 的构造函数只负责初始化DerFoo 的直接子对象,即dbFoo,它是DerFoo 的基类。 Foo的构造函数反过来负责初始化b

事件的顺序是这样的:

  • DerFoo的构造函数调用Foo的构造函数
  • Foo 的构造函数调用b 的构造函数
  • Foo 的构造函数运行它的主体,留下 Foo 对象完全构造
  • DerFoo的构造函数调用db的构造函数
  • DerFoo 的构造函数运行它的主体,使 DerFoo 对象完全构造。

如果在DerFoo 构造函数中,您不喜欢Foo 构造函数在b 中留下的值,您可以分配一个新值给b,使用这些语法中的任何一种:

b = Bar(47);
this->b = Bar(47);
this->Foo::b = Bar(47);
Foo::b = Bar(47);

【讨论】:

  • 非常感谢这个回答,非常详细。
【解决方案3】:

我觉得这个问题不是很清楚,但让我们看看我是否理解你想要做什么以及如何做。

DerFoo::DerFoo(int x) : Foo(x), [a]
    db(x+3) 
    // db(4.0,30)          [1]
    // note: candidates are Bar::Bar(const Bar&), Bar::Bar(int)

    // b(x-3)              [2]
{
    //this->b(x - 3);      [3]
    //this->db(x + 3);     [4]
}

第一个错误是 [1],编译器告诉你没有 Bar 的构造函数,它同时接受一个 double 和一个 int。该错误还列出了您可以使用的两个可能的构造函数:Bar(int)Bar(Bar const &)。我不确定您对这条线的意图是什么,但您已经知道(上一行)只需提供 int 调用就可以工作。

[2] b 不是DerFoo 的成员,因此无法在DerFoo 的初始化列表中初始化。 Foo 负责初始化它自己的成员,这将通过调用 [a] 中的 Foo 构造函数来实现。

[3],[4],两个表达式都采用this->member(i) 的形式。在初始化期间,语法member(i) 会很好,用i 的值初始化member。在初始化之外,语法意味着调用operator()( int ) 传递i 的值。这些成员已经被初始化,但如果你想重置它们,你需要assign而不是初始化它们。

【讨论】:

  • 感谢您的回答。如您所见,它们都被注释掉了,这是我展示我尝试过但知道会失败的方法的一种方式。但是非常感谢您说得很清楚:)
【解决方案4】:

b 在 Foo 类中。访问它(肯定)使用

Foo::b = Bar(x-3);

【讨论】:

  • b 不是静态的,因此范围运算符在这里没有意义。 this->b 或只是 b 是这里需要的。这根本不能解释为什么this->b(x-x) 不起作用(它是一个函数调用而不是一个赋值)。
  • 确实有道理。当有 2 个超类具有相同的字段 b 时,你会怎么做?不要再表现出你的无知了。
  • b 没有两个字段。在Foo 中有一个字段b,在DerFoo 中有一个字段db。所以额外的范围界定没有意义,因为它没有消除歧义。您还默默地将函数调用更改为实际分配,而没有提及它...您描述的修复是虚假的,但是一个字都没有提及正确的修复(您也在代码中做了)。因此投反对票。 SO不仅是为了给出正确的代码,也是为了解释。
  • 证明我的无知,通过向我展示上面代码中需要消除歧义的b 的地方,我很乐意删除反对票。如果你不能,那么反对票就会保留。
【解决方案5】:

您没有必须使用初始化列表,此时您肯定也应该使用初始化列表。

在构造对象时,在输入构造函数的代码之前,所有成员变量都已经构造好了。如果你不提供初始化器,它们将被默认构造。

同样你不能在已经构造变量之后再次构造它。你的

this->b(x+3)

不是告诉编译器构造b,而是告诉它在你的对象上调用一个名为b的函数。您的类中不存在这样的函数,因此出现错误。请注意,一旦将对象构造为变量,就无法再次调用该变量的构造函数(仅更改值)。

可以像在大多数语言中一样使用= 更改值。因此你可以这样做:

Foo::Foo(int x)
{
   this->b = Bar(x+3);
}

这意味着您正在创建另一个无名 Bar 对象并将其值分配给 this->b。您应该知道,这意味着,在创建Foo 时,您将创建两个Bar 对象。首先是默认构造的,之前。输入构造函数代码,然后输入新的无名代码。然后你最终将值分配给已经构造的对象,所以这段代码比使用初始化列表的代码效率低得多。

编辑

因为我错过了上面代码中的第二个doesn't work,所以这里有一些额外的信息:

您还尝试在派生的DerFoo 对象的构造函数中直接初始化b。但是,一旦达到这部分代码,它就已经被构造了。因此,任何在派生构造函数中构造它的尝试都为时已晚。

因此,您要么必须向Foo 添加另一个构造函数,该构造函数获取值并在DerFoo 的构造函数中使用该构造函数。此解决方案更可取,因为它只会在b 中构造Bar 对象一次。如果不能添加这样的构造函数,则需要在构造函数代码中对DerFoo使用赋值。

即使使用范围运算符,尝试直接在 DerFoo 构造函数中初始化 b 也不会起作用。

DerFoo::DerFoo() : Foo::b(x-3) {}

仍然会产生错误:http://ideone.com/6H8ZD

【讨论】:

  • 看来他想在子类中初始化b。所以你真的没有帮助
  • @UmNyobe - 我不清楚他想要在子类中初始化b。我认为他误解了他必须在子类中初始化b
  • UmNyobe:是的,你给出的台词绝对正确,但也绝对没用。没有真正解释什么是错误的,什么是初始化,什么是赋值。不要只修复别人,还要告诉他们哪里出了问题以及如何改进。
  • @UmNyobe:我们似乎在谈论不同的台词。代码中有多个doesn't work cmets,所以这可能会造成混淆。我在谈论基类中的doesn't work。我想我会在这方面编辑我的答案。
  • 谢谢。我考虑完全按照您的建议做,使基类的构造函数变得巨大,因为还有更多需要初始化的继承变量,但我认为可以以更好的方式完成。谢谢你的回答。
猜你喜欢
  • 2014-10-23
  • 2011-08-09
  • 2011-05-20
  • 2014-04-02
  • 2020-08-05
  • 2018-01-30
  • 1970-01-01
  • 2012-03-02
  • 1970-01-01
相关资源
最近更新 更多