1

I'm trying to instantiate a class within a class, so that the outer class contains the inner class.

This is my code:

#include <iostream>
#include <string>

class Inner {
    private: 
        std::string message;

    public:
        Inner(std::string m);
        void print() const;
};

Inner::Inner(std::string m) {
    message = m;
}

void Inner::print() const {
    std::cout << message << std::endl;
    std::cout << message << std::endl;
}

class Outer {
    private:
        std::string message;
        Inner in;

    public:
        Outer(std::string m);
        void print() const;
};

Outer::Outer(std::string m) {
    message = m;
}

void Outer::print() const {
    std::cout << message << std::endl;
}

int main() {
    Outer out("Hello world.");
    out.print();

    return 0;
}

"Inner in", is my attempt at containing the inner within the outer, however, when I compile, i get an error that there is no matching function for call to Inner::Inner(). What have I done wrong?

Thanks.

Joseph Salisbury
  • 2,047
  • 1
  • 15
  • 21

3 Answers3

6

You need to use initialization lists to initialize class members:

Inner::Inner(const std::string& m)
  : message(m)
{
}

Outer::Outer(const std::string& m)
  : in(m)
{
}

(Note that I passed the strings per const reference, which is better than passing them by value. See this answer for how to pass function arguments.)

This way you can specify exactly which constructors should be called for class members.

If you don't specify a constructor, the default one will be called implicitly. Assigning to the object later will then invoke the assignment operator and override whatever the default constructor initialized the object to. That's wasting performance at best.
Since our Inner doesn't have a default constructor (declaring any constructor prevents the compiler from defining a default constructor by itself), it cannot be called, so you need to specify the constructor taking a string explicitly.


Edit: Note that, if you have more than one class member, they are all initialized that way, separated by commas:

Outer::Outer(const std::string& m) : in1(m), in2(m), in3() {}

Note that the order of initialization of class members is determined by their declaration order within the class definition, not by the order they appear in the initialization list. It's best to not to rely on initialization order, since changing that in the class definition would then create a very subtle bug in the constructor's definition. If you can't avoid that, put a comment besides the class members declaration:

class outer {
  public:
    outer(const inner& in) 
     : in_(in), rin_(in_) // depends on proper declaration order of in_ and rin_
    {}
  private:
    inner in_;   // declaration order matters here!1
    inner& rin_; // (see constructor for details)
};

Base class constructors are specified the same way, and they are initialized before class members, also in order of declaration in the base class list. Virtual base classes, however, are initialized before all non-virtual base classes.


Destructors, BTW, are always called in the reverse order of constructors. You can rely on that.

Community
  • 1
  • 1
sbi
  • 219,715
  • 46
  • 258
  • 445
  • -1. You don't need to use initializer lists to initalize class members. The issue is the missing default constructor. There are several ways to address it - using an initializer list where you pass in the initial value for the Inner field is just one. – iheanyi Jul 29 '14 at 15:42
  • 1
    @iheanyi: In order to solve the problem as stated, you need to. Otherwise `Inner` would not have an initialized data member. Also, my classes quite often do not exhibit a default constructor. That's because for many of my classes an initialized-but-data-not-populated state makes no sense. YMMV, but adding default constructors just to avoid listing data members in the initialization list certainly seems a very wrong advice to me. – sbi Jul 30 '14 at 04:56
  • I think my issue is that you presented a solution, but didn't fully address the issue the OP had - did not understand that the Inner object needs to be constructed and the compiler will not provide a default here. So, he either needs to write one, or explicitly initialize Inner's field in an initializer list rather than in the body of a constructor since string is not default constructible but needs to be constructed before code in the body of a cosntructor will run. I agree, default constructors are not always desired or the solution. – iheanyi Jul 30 '14 at 17:46
  • @sbi I realise this was written in 2010, but your advice to pass the string to the function by constant reference is no longer entirely valid. See Herb Sutter's talk (https://www.youtube.com/watch?v=xnqTKD8uD64) starting at 1 hour and 7 minutes. The optimal way to pass it (to deal with all the types passed to it, rvalues, lvalues, etc) involves a mess of template metaprogramming. – user1997744 Jan 05 '16 at 17:22
  • @user1997744: Yes, see [this](http://stackoverflow.com/a/2139254/140719) FAQ entry. – sbi Jan 06 '16 at 23:16
1

Since Inner has no default constructor, you need to initialize it explicitly. The way to do this, as @sbi pointed out, is using the initialization list in the constructor.

Péter Török
  • 114,404
  • 31
  • 268
  • 329
-1

You could also add a constructor to Inner that takes no argument. It's implicitly trying to call it since you're not explicitly initializing in in Outer. If in were a pointer (Inner *in), then it would work.

Basically if you write

Foo f;

in C++, it will call the default constructor (Foo::Foo()).

  • But then it would first default-initialize the object just to immediately overriding that value with the assignment operator. Sorry, but IMO that's bad advice, better use initialization lists to specify the right constructor. -1 from me. – sbi May 30 '10 at 11:59
  • oh, that's true. just wanted to point out that this is also possible (and in general terms might be needed). – Christopher Bertels May 30 '10 at 12:09
  • +1 @sbi Perhaps in this specific case it would. But as a general rule, using a default constructor is a matter of appropriateness, not necessarily a bad thing. – iheanyi Jul 29 '14 at 15:45
  • A default ctor most certainly isn't "a matter of appropriateness", or, if it is, then often only in the sense that for many classes its inappropriate to have one. – sbi Jul 30 '14 at 04:58