Instantiating a class within a class

2019-04-16 18:51发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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()).



标签: c++ class