1

We have a library with that publishes an abstract base class:

(illustrative psudocode)


/include/reader_api.hpp

class ReaderApi
{
public:
  static std::unique_ptr <ReaderApi> CreatePcapReader ();
  virtual ~ReaderApi() = 0;
};

In the library implementation, there is a concrete implementation of ReaderApi that reads pcap files:

/lib/pcap_reader_api.cpp

class PcapReaderApi
: 
  public ReaderApi
{
public:
  ReaderApi() {};
};

Client code is expected to instantiate one of these PcapReaderApi objects via the factory method:

std::unique_ptr <ReaderApi> reader = ReaderApi::CreatePcapReader ();

This feels gross to me on a couple of levels.

First, the factory method should be free, not a static member of ReaderApi. It was made a static member of ReaderApi to be explicit about the namespaces. I can see pros & cons either way. Feel free to comment on this, but it's not my main point of contention.

Second, my instinct tells me I should be using std::make_unique rather than calling a factory method at all. But since the actual object being made is an implementation detail, not part of the public headers, there's nothing for the client to make_unique.

The simplest solution, in terms of understandability and code maintenance, appears to be the solution I've already come up with above, unless there is a better solution that I'm not already aware of. Performance is a not a major consideration here since, because of the nature of this object, it will only be instantiate once, at startup-time.

With code clarity, understandability, and maintainability in mind, is there a better way to design the creation of these objects than I have here?


I have considered two alternatives that I'll go over below.

One alternative I've considered is passing in some kind of identifier to a generic Create function. The identifier would specify the kind of object the client wishes to construct. It would likely be an enum class, along these lines:

enum class DeviceType
{ 
  PcapReader
};

std::unique_ptr <ReaderApi> CreateReaderDevice (DeviceType);

But I'm not sure I see the point of doing this versus just making the create function free and explicit:

std::unique_ptr <ReaderApi> CreatePcapReader ();

I also thought about specifying the DeviceType parameter in ReaderApi's constructor:

class ReaderApi
{
public:
  ReaderApi (DeviceType type);
  virtual ~ReaderApi() = 0;
};

This would enable the make_unique idiom:

std::unique_ptr <ReaderApi> reader = std::make_unique <ReaderApi> (DeviceType::PcapReader);

But this obviously would present a big problem -- you're actually trying to construct a ReaderApi, not a PcapReader. The obvious solution to this problem is to implement a virtual constructor idiom or use factory construction. But virtual construction seems over-engineered to me for this use.

Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • 2
    I am not sure what the question really is. Don't you like the fact that the factory is implemented as a static method? Or, you don't like the factory pattern? – BЈовић Nov 12 '14 at 16:06
  • If we had partial specialization for function-templates, things would be simple... – Deduplicator Nov 12 '14 at 16:07
  • 2
    @BЈовић Having the factory as a static method kind of breaks the factory pattern in a fundamental way: The base class, which provides the factory method has to know about the concrete class in order to implement the factory. This is often undesirable and may defeat the purpose of having a factory in the first place. – ComicSansMS Nov 12 '14 at 16:09
  • @BЈовић: Sorry, I know that I prattled on a bit and I was likely being vague. My problem is that my concerns are also vague. I mean this thing works, but just "feels wrong" to me. I was hoping for some insight that lead me to a solution that didn't "feel wrong." – John Dibling Nov 12 '14 at 16:15
  • Right now I'm thinking that a decent and simple solution is to just put all this stuff in headers. That would obviate the need for the factory in the first place. It would also mean that I could only use C++03 stuff in the implementation, but that's not that big of a deal to me. – John Dibling Nov 12 '14 at 16:17
  • 2
    I don't see a problem with the solution you have. Given the choice, I'd make `CreatePcapReader()` a free function in the same namespace as `ReaderApi()`. As for not using `make_unique`, the reason we preach using it in the first place is to avoid the use of `new` in user code, and to avoid having raw owning pointers lying around even for a short while, and you've already achieved all that. If it makes you feel better, you could rename the factory function `make_unique_pcap_reader()` :) – Praetorian Nov 12 '14 at 16:30
  • I fully agree with @Praetorian. In particular, there is no benefit from using `make_unique` (unlike `make_shared`). However, whether or not the factory function is best free or a static member of the base depends IMHO the supposed use case. A free function would be the choice for the plug-in pattern, when the plug-in .so file provides its own factory function. In all other situations, I'd prefer the static member to base, as it shows clearly the purpose and the relation to the base. – Walter Nov 12 '14 at 16:38
  • Isn't this question *primarily opinion-based*? – Walter Nov 12 '14 at 16:39
  • @ComicSansMS The only thing that is WTF here is : why the base class implements the factory method? Shouldn't there be a separate factory class? – BЈовић Nov 12 '14 at 17:08
  • @BЈовић: That's also a possibility. – John Dibling Nov 12 '14 at 17:10
  • @BЈовић My thoughts exactly. Just didn't want to put it that drastically :) – ComicSansMS Nov 12 '14 at 19:17
  • @Walter: Not if we limit the scope to technical ramifications of different approaches. My opinion is based on the technical aspects anyway. – John Dibling Nov 12 '14 at 23:09

1 Answers1

1

To me, the two options to consider are your current approach, or a namespace level appropriately named free function. There doesn't seem to be a need for an enumerated factory unless there are details you aren't mentioned.

Using make_unique is exposing implementation details so I would definitely not suggest that approach.

Mark B
  • 95,107
  • 10
  • 109
  • 188