0

Let's say you have a class which have members which should always be set (i.e. defaults would leave it in a bad state). To prevent having objects with "bad state", you have a constructor which requires everything to be set. The issue is, that you also have a set of "factory" classes, create the objects by parsing some data stream and setting members directly. You want to be explicit about which classes can construct your objects without using the constructor. Is there a good pattern for this?

Example:

enum class DataState
{
     STATE_ONE,
     STATE_TWO
};

class Data
{
public:
   Data(std::string _dp1, DataState _dp2): dp1(_dp1), dp2(_dp2) {}
   Data() {}; /* Don't want this constructor because it forces a default for dp2

   std::string dp1;
   DataState dp2;
};

The default constructor is needed because you have factory classes which get callbacks with a "Data" object, and fill it's members on a per member basis. .i.e. something like:

Field1Callback(Data &d, const std::string &fieldVal)
{
    d.dp1 = field;
}

Field2Callback(Data &d, const std:string &fieldVal)
{
    d.dp2 = ConvertToState(fieldVal);
}

Are there any patterns for being explicit about what classes are allowed to call "default" constructors? i.e. serializers and parsers?

To clarify, here is some snippet code for a templated parser that I'm working with:

template<typename R>
class CSVParser
{
   public:
   CSVParser(std::vector<std::pair<std::string, std::function<bool(const std::string &field, R &rec)>>> parsers);
   bool ProcessBuffer(const unsigned char *buffer, size_t length, size_t &bytes_parsed, bool last_buffer, std::function<void(const R& onNewRecord)> onNewRecord)
};

Example construction for parser for Data could be:

 CSVParser<Data> d(
 {
   {
    "dp1", 
    [](const std::string &field, Data &rec) { rec.dp1 = field; return true;}
   },
   {
     "dp2", 
     [](const std::string &field, Data &rec) { rec.dp2 = ConvertToState(field); return true;}
   }
}
);

Couple of solutions would be:

  1. Make default constructor private, add an empty friend class, and then have instantiation of CSVParser which derives from friend.
  2. Move all members for Data to a class DataRaw which exposes everything, but only use DataRaw for parsers, and use Data everywhere else in the program.
  3. Create a DataSerialize class which derives from Data and passes garbage into constructor, knowing that it will overwrite it. Callbacks to handle constructed Data object will get passed a DataSerialize object by ref, and never know the difference.

This seems to be a common issue in other languages as well, where factory objects need to be able to break the public/private boundaries.

bpeikes
  • 3,495
  • 9
  • 42
  • 80
  • perhaps this question will point you in the right direction https://stackoverflow.com/questions/25922313/restricting-access-to-c-constructor-and-destructor – RisingSun Dec 02 '19 at 21:40
  • 3
    Why can't those factory classes construct the object, once they have all of the values, for the mandatory fields? – Algirdas Preidžius Dec 02 '19 at 21:40
  • Could you not implement a static method on `Data`, whose input argument is the serializer / archiver you want to use for constructing the object? Then a constructor can be private. – Joakim Thorén Dec 02 '19 at 21:44
  • @JoakimThorén - That's not going to work in this case. The class I'm trying to get to work with classes which do not have default constructors is a template class which implements a generic CSV parser. This template is instantiated with the class which represents the objects that are serialized out of the CSV. – bpeikes Dec 03 '19 at 03:27
  • A factory should not fill an empty object. It should construct and return a valid object using a regular constructor, just like everyone else does. Being a factory doesn't make you a special snowflake. You can add a constructor that accepts a stream if need be. – n. m. could be an AI Dec 03 '19 at 06:20

1 Answers1

0

If you really want to be explicit about which classes can call the default constructor, you can do the following:

#include <memory>

class Factory;

void create_data(Factory &);

class Data {
    Data() = default;

    friend void create_data(Factory &);
};

class Factory {

    std::unique_ptr<Data> _data;

public: 
    void set_data(std::unique_ptr<Data> &&inp) {
        _data = std::move(inp);
    }

    std::unique_ptr<Data> make_data();

};

void create_data(Factory &f) {
    f.set_data(std::unique_ptr<Data>(new Data())); // make_unique does _not_ work.
}

std::unique_ptr<Data> Factory::make_data() {
    create_data(*this);
    return std::move(_data);
}

int main() {
    Factory f;

    auto d = f.make_data();
}

But I don't think I would recommend it.

n314159
  • 4,990
  • 1
  • 5
  • 20
  • I thought about friend classes, but I'd like to avoid it as I'm looking for a solution that would work in the case where I really can't touch the `Data` object code. – bpeikes Dec 03 '19 at 04:05