2

So I have searched all over the place and I can not seem to find the answer to this specific question. I am using a winXP with cygwin and gcc 3.4.4 cygming special.

Problem: I have a class that works as an interface with some abstract methods and protected variables that should be in every class that inherits from this class. Now I also have another class that is a member variable of this interface.

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

So the problem as you can see is inside the derived foo class, DerFoo how to initialize b. I have tried member initialization method, but then the compiler doesn't realize about protected variables. So then for some bizarre reason unbeknownst to me, it can not find the constructor in this class. Even though if were to include a "wrong" call to the constructor of a protected member variable (non inherited) it would suggest the correct version of the constructor.

I have no clue still how to do this. Any help is greatly appreciated.

tomasgudm
  • 87
  • 3
  • I'm confused - with a couple minor tweaks this compiles fine on ideone (http://ideone.com/2cLUa). What error are you getting and where? – tmpearce Mar 29 '12 at 15:43
  • 2
    Ok, I think I see where you're getting confused. In the code you posted, the `Foo::b` member variable **is** being initialized when you call `Foo(x)`. You can add a debug message to the `Bar` constructor and you'll see it is initialized twice - once for `b` and once for `db`. Once `b` is initialized, you can't re-initialize, you have to assign a new value to it using `=` (assignment operator). – tmpearce Mar 29 '12 at 16:16
  • Right. Thanks all for your answers, very insightful and helpful. Since some suggest this is "very inefficient", my real code has many variables at the interface level. That might be a bad design by me, but I don't think it will matter for this project. So I think I will go with this choice, `Foo(int b, int c, double d)...` for all my member variables and then do `DerFoo(int derA, derX) : Foo (b,c,d) { ...` – tomasgudm Mar 29 '12 at 19:19
  • @tomasgudm: There is another way to get your code to be more efficient: In your case you are passing data from the calling code to `DerFoo`s constructor to `Foo`s constructor to `Bar`s constructor which is then using it to constuct the actual object. This means your data passes three layers before being used. If you instead create the `Bar` object directly in `DerFoo` constructor, pass this to the `Foo` constructor and then `std::move` it (C++11) into the `b` field, you will effectively reduce the level of layering and number of parameters, while staying efficient. – LiKao Mar 30 '12 at 08:06

5 Answers5

3

After you declare a variable you have to set it otherwise you will be calling it like a function.

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

The preferred way is to use the initializer list to avoid unnecessary copies of Bar. If you do, however, need to set b outside of the constructor the above example is how to do so.

Joe
  • 56,979
  • 9
  • 128
  • 135
  • Thanks, I thought I had tried that, but apparently other bugs in my code (that I have now fixed) prevented it to work at that time. I was just going nuts, thanks for this. – tomasgudm Mar 29 '12 at 15:43
  • This constructs `Bar` twice, which is absolutely inefficient in the situation that was described. – LiKao Mar 29 '12 at 15:53
  • This constructs a `Bar` at least thrice. Once for `b`, once for `db`, and once during the assignment. – Robᵩ Mar 29 '12 at 16:33
  • There is definitely 2 constructions occurring and possibly a 3rd if the compiler does not do copy elision (or is not C++11) but based on the OP's code this is the only option without using the initializer list unless `Bar` offers a set `y` method. – Joe Mar 29 '12 at 17:55
  • @Rob: Yes, we were talking about different lines. I was talking about the base class constructor, and missed that there was another `doesn't work` in the derived class constructor. In the base class there would be two `Bar`s which are constructed and in the derived class three. – LiKao Mar 29 '12 at 18:03
2

DerFoo's constructors must not (and cannot) initialize b, that's Foo job. DerFoo's constructors are responsible for initializing only DerFoo's immediate subobjects, namely db and the Foo which is a base class of DerFoo. Foo's constructor, in turn, is responsible for initializing b.

The sequence of events is like:

  • DerFoo's constructor invokes Foo's constructor
  • Foo's constructor invokes b's constructor
  • Foo's constructor runs its body, leaving the Foo object completely constructed
  • DerFoo's constructor invoke's db's constructor
  • DerFoo's constructor runs its body, leaving the DerFoo object completely constructed.

If, in the DerFoo constructor, you don't like the value that the Foo constructor left in b, you can assign a new value to b, using any one of these syntaxes:

b = Bar(47);
this->b = Bar(47);
this->Foo::b = Bar(47);
Foo::b = Bar(47);
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
1

I don't find the question very clear, but let's see if I understood what you are trying to do and how.

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

The first error is [1], where the compiler is telling you that there is no constructor of Bar that takes both a double and an int. The error also lists the two possible constructors that you can use: Bar(int), Bar(Bar const &). I am unsure as of what you intended with this line, but you have already figured out (previous line) that by just providing an int the call will work.

[2] b is not a member of DerFoo, and thus cannot be initialized in the initializer list of DerFoo. It is the responsibility of Foo to initialize it's own member and that will happen through the call to the Foo constructor in [a].

[3],[4], both expressions take the form this->member(i). During initialization the syntax member(i) will well, initialize member with the value of i. Outside of initialization the syntax means call operator()( int ) passing the value of i. Those members have already been initialized, but if you want to reset them you need to assign rather than initialize them.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thanks for your answer. As you can see, they are all commented out, a way for me to show methods that I have tried and know to fail. But thanks a lot for making it very clear however :) – tomasgudm Mar 29 '12 at 19:14
0

b is inside the Foo class. to access it (for sure) use

Foo::b = Bar(x-3);
UmNyobe
  • 22,539
  • 9
  • 61
  • 90
  • `b` is not static, so the scope operator does not make sense here. `this->b` or just `b` is what is needed here. This does not explain at all why `this->b(x-x)` does not work (it being a function call instead an assignment). – LiKao Mar 29 '12 at 15:51
  • it does make sense. What do you do when there are 2 superclasses with the same field `b`? Stop showing your ignorance. – UmNyobe Mar 29 '12 at 16:14
  • There are no two fields `b`. There is a field `b` in `Foo` and a field `db` in `DerFoo`. So the additional scoping does not make sense, because it is disambiguating nothing. Also you silently changed the function call into an actual assignment, without any mentioning of it... The fix you described is bogus, but the correct fix (you did in your code as well) is not mentioned with a single word. Hence the downvote. SO is not only for giving correct code, but also for explaining. – LiKao Mar 29 '12 at 17:58
  • Proof my ignorance, by showing me the place where there is any ambigous `b` in the above code which needs disambiguation, and I will gladly remove the downvote. If you cannot, then the downvote stays. – LiKao Mar 29 '12 at 18:05
-1

You don't have to use an initializer list, you definitely also should use an initializer list at this point.

At construction of an object, before the code of the constructor is entered all member variable are already constructed. If you don't give initializers, they will be default constructed.

Also you cannot construct a variable again, after it already has been constructed. Your

this->b(x+3)

is not telling the compile to construct b it is telling it to call a function named b on your object. Such a function does not exist in your class, hence the error. Note that once an object has been constructed into a variable, there is no way, to ever call the constructor for that variable again (only to change the value).

A value can be changed using = as in most languages. Hence you could do:

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

This means you are creating another nameless Bar object and assigning it's value to this->b. You should be aware, that this means, you will create two Bar objects, when creating a Foo. First the default constructed one, before. The constructor code is entered, then the new nameless one. Then you will finally assign the value to the already constructed object, so this code is much more inefficient than the one, which uses initializer lists.

EDIT:

Since I missed the second doesn't work in the code above here some additional information:

You are also trying to initialize b directly in the constructor of the derived DerFoo object. However once this part of code is reached, it has already been constructed. Hence any attempt to construct it in the derived constructor comes to late.

Hence you either have to add another constructor to Foo which takes the value and use that one in your constructor of DerFoo. This solution is preferable, since it will only construct the Bar object in b once. If you cannot add such a constructor, then you need to use an assignment in the constructor code for DerFoo.

Trying to initialize b directly in the DerFoo constructor will not work even if the scoping operator is used.

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

will still produce an error: http://ideone.com/6H8ZD

LiKao
  • 10,408
  • 6
  • 53
  • 91
  • It seems he wants to initialize b in the subclass. So really you are not helping – UmNyobe Mar 29 '12 at 16:18
  • @UmNyobe - It isn't clear to me that he *wants* to initialize `b` in the subclass. I think he misunderstands that he *must* initialize `b` in the subclass. – Robᵩ Mar 29 '12 at 16:35
  • UmNyobe: Yes, the lines you gave was absoluteley correct, but also absolutely useless. There was no real explanation of what is wrong, what is initialisation and what is assignment. Do not just fix others, but also tell them what is wrong and how they can improve. – LiKao Mar 29 '12 at 18:01
  • @UmNyobe: Also it seems we are talking about different lines. There are multiple `doesn't work` comments in the code, so this may be confusing. I was talking about the `doesn't work` in the base class. I guess I will edit my answer to that respect. – LiKao Mar 29 '12 at 18:16
  • Thanks for this. I thoughtabout doing exactly what you suggested, making the base class' constructor huge, since there are many more inherited variables that need initializing, but I thought it could be done in a better way. Thanks for your answer. – tomasgudm Mar 29 '12 at 19:11
  • @tomasgudm: If your base constructor get's very huge (let's say about more than 5 parameters, although this is debateable), this often means your class is responsible for too much (see the God Object anti-pattern: http://en.wikipedia.org/wiki/God_object). However without knowing more about your code it is impossible to tell, if there is a better way to handle this situation. – LiKao Mar 30 '12 at 08:00