views:

137

answers:

6

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?

+1  A: 

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.

Dan Olson
I think `#include <iosfwd>` might be what one is looking for (for forward declaring streams).
UncleBens
+1  A: 

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.

anon
Okay, that's right. When I wrote this I was thinking of the discussions about member functions versus non-member friend functions, and how some people advise against using too many member functions. But I guess that's just for aesthetic reasons ...
It's not for aesthetic reasons, it's to limit the number of things that need to change when the class's internal representation changes. That is, to increase encapsulation. Scott Meyer has an article on this floating around.
Dan Olson
@Dan: That doesn't get you encapsulation. If a member only needs access to the public interface, that's all you need to use when implementing it; you don't have to access non-publics directly just because they're there. What it does give you is this being checked by the compiler instead of a cursory glance (name non-publics distinctly and it's easy to glance), and that doesn't have nearly enough value for me to let it affect member vs non-member. (There are important reasons to make things non-members; it's just that what you said is often cited yet not one of them.)
Roger Pate
+9  A: 

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>());
UncleBens
A: 

Builder Pattern is what you are looking for.

Hope this helps!

Mihir Mathuria
A: 

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.

Roger Pate
+1  A: 

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

Matthieu M.