Overall design: I have an aggregate class C
that contains N
member variables of type M_i, i = 1 ... N
that each have a common write-only update()
interface as well as class-specific read-only accessor functions [F]un_i(), [F] = any letter, i = 1 .. N
(they do not have such regular names in reality). Each of the member types M_i
forms an independent abstraction of its own, and is used elsewhere in my program.
The aggregate class needs to update all the members in a single transaction, so it has an update()
function of its own calling the update()
member function of all its member variables.
// building blocks M_i, i = 1 ... N
class M_1
{
public:
// common write-only interface
void update();
// M_1 specific read-only interface
int fun_1() const;
// ...
int fun_K() const;
private:
// impl
};
// ...
class M_N
{
public:
// common write-only interface
void update();
// M_N specific read-only interface
int gun_1() const;
// ...
int gun_K() const;
private:
// impl
};
// aggregate containing member variables M_i, i = 1 ... N
class C
{
public:
// update all members in a single transaction
void update()
{
m1_.update();
// ...
mN_.update();
}
// read-only interface?? see below
private:
M_1 m1_;
// ...
M_N mN_;
};
Question: the do I access the various member functions of the various member variables in the aggregate class? I can think of three alternatives:
Alternative 1: write N * K
delegates to all K
member functions of all N
member variables
class C
{
int fun_1() const { return m1_.fun_1(); }
// ...
int fun_K() const { return m1_.fun_K(); }
// ...
int gun_1() const { return mN_.gun_1(); }
// ...
int gun_K() const { return mN_.gun_K(); }
// as before
};
int res = C.fun_5(); // call 5th member function of 1st member variable
Alternative 2: write N
accessors to all N
member variables
class C
{
M_1 const& m1() const { return m1_; }
// ...
M_N const& mN() const { return mN_; }
// as before
};
int res = C.m1().fun_5(); // call 5th member function of 1st member variable
Alternative 3: write 1
accessor template to all N
member variables
class C
{
public:
enum { m1, /* ... */ mN };
template<std::size_t I>
auto get() const -> decltype(std::get<I>(data_))
{
return std::get<I>(data_);
}
private:
std::tuple<M_1, /* ... */ M_N> data_;
};
int res = C.get<m1>().fun_5(); // call 5th member function of 1st member variable
Alternative 1 avoids violating the Law of Demeter but it needs an awful lot of tedious boiler plate code (in my application, N = 5
and K = 3
, so 15
delegating wrappers). Alternative 2 cuts down on the number of wrappers, but the calling code feels a little uglier to me. But since all that code is read-only, and modfications can only happen through the consistent aggregate update()
, my current opinion that Alternative 2 is preferable to Alternative 1 (and at least safe). If that's the case, then a fortiori, Alternative 3 should be the best choice since it uses only a single accessor and has the same safety guarantees as Alternative 2.
Question: what is the preferred interface for this type of code?
SEQ_FOR_EACH
andLOCAL_MACRO
, for example. I created a small sample. – Nilsanilsen