Frank
Frank

Reputation: 66194

How to provide many constructors, but without too many dependencies?

This is a pretty basic C++ design question:

I have a class that contains some data which is read-only, once the object is constructed:

class Foo {
private:
  class Impl;
  Impl* impl_;  
public:
  int get(int i); // access internal data elements
};

Now, I'd like to implement several ways to construct a Foo object and fill it with data: from std::istream, from an iterator, a vector, etc. What's the best way to implement that?

I could just add all those constructors directly in Foo, but I don't really want the Foo user to have to include std::istream etc. I'm also worried about classes containing too much code.

What is the most idiomatic way to do this? I guess, add some private addElement function, and then define friend factory functions that create Foo objects by reading data, calling addElement and return the constructed object? Any other options?

Upvotes: 3

Views: 274

Answers (6)

Matthieu M.
Matthieu M.

Reputation: 299930

There is one simple solution: the use of another class as a go-between.

struct FooBuild
{
  // attributes of Foo
};

class Foo
{
public:
  Foo(const FooBuild&);

private:
  // attributes of Foo, some of them const
};

Then anyone can easily set up FooBuild as one wishes, and construct a Foo object from that. This way you don't have to provide too many constructors, and you can still maintain an invariant class for Foo easily, with first validation occuring in the constructor as usual.

I took the idea from python, and its frozenset class :)

Upvotes: 1

Roger Pate
Roger Pate

Reputation:

You're talking about two different kinds of code, 1) source code size (which affects build time only) and 2) executable size ("compiled code" size, or the code segment). They are correlated, but definitely not the same.

The first is a hard problem to solve in C++, because the language requires monolithic, standalone TUs. (Compare to a language like Go, where the designers learned to avoid this issue from their C experience.) Templates help, as do only using forward declarations ("declarations that are not definitions") when you don't need definitions. (Though it's ironic that templates help, as they require all of their code in headers, in practice.) Mostly long build times are something we just deal with in current C++.

The second can be mitigated with either a common initialization method each ctor can call or a common base. The latter has other advantages, such as when the initialization of members can or must be done in the initialization list. Example:

struct SpanBase {
  SpanBase(int start, int stop, int step)
  : start(start), stop(stop), step(step)
  // any complex code in the init list would normally be duplicated
  // in each Span ctor
  {
    IMAGINE("complex code executed by each Span ctor");
    if (start > stop) throw std::logic_error("Span: start exceeds stop");
  }
protected:
  int start, stop, step;
};

struct Span : protected SpanBase {
  // Protected inheritance lets any class derived from Span access members
  // which would be protected in Span if SpanBase didn't exist.  If Span
  // does not have any, then private inheritance can be used.

  Span(int stop) : SpanBase(0, stop, 1) {}
  Span(int start, int stop) : SpanBase(start, stop, 1) {}

  Span(int start, int stop, int step): StepBase(start, stop, step) {}
  // this one could be handled by a default value, but that's not always true
};

And finally, C++0x allows you to delegate from one ctor to another, so this whole pattern is vastly simplified.

Upvotes: 0

Mihir Mathuria
Mihir Mathuria

Reputation: 6539

Builder Pattern is what you are looking for.

Hope this helps!

Upvotes: 0

UncleBens
UncleBens

Reputation: 41331

If you want to construct something from a range, perhaps:

class X
{
public:
    template <class InputIterator>
    X(InputIterator first, InputIterator last);
};

Usage:

//from array
X a(array, array + array_size);

//from vector
X b(vec.begin(), vec.end());

//from stream
X c((std::istream_iterator<Y>(std::cin)), std::istream_iterator<Y>());

Upvotes: 10

anon
anon

Reputation:

I could just add all those constructors directly in Foo, but I don't really want the Foo user to have to include std::istream etc.

If they are constructed using istream, you have to use the relevant header files in the files that actually use the istream classes.

I'm also worried about classes containing too much code; the user may want to create many Foo objects, but I don't want each object to contain lots of construction code.

You seem confused. Each object doesn't contain a copy of the code - there is only ever one copy.

Upvotes: 1

Dan Olson
Dan Olson

Reputation: 23377

I could just add all those constructors directly in Foo, but I don't really want the Foo user to have to include std::istream etc. I'm also worried about classes containing too much code; the user may want to create many Foo objects, but I don't want each object to contain lots of construction code.

Your constructor for std::istream can forward declare it and pass it as a reference. This way your user doesn't need to include istream to include your Foo.h, but you do need to include istream in your Foo.cpp.

I'm not sure I understand your second objection. Objects don't carry code around with them, only data. Assuming we're not talking about templates, the code exists only once. If a constructor isn't used by the program the linker should dead strip it. There should be no waste from providing many constructors.

Upvotes: 1

Related Questions