1

Is there a way to ensure that only class Fabric can construct the class Foo and all of its sub-classes, without having to declare a private constructor and friend class Fabric in each sub-class?

struct Foo {
   friend class Fabric;
protected:
   Foo() = default;
};

struct FooConcrete1 : Foo {
   friend class Fabric;
protected:
   Foo() = default;
};

Since friendship is not inherited, both manual actions when declaring each sub-class seem to be needed, which is error-prone.

Violet Giraffe
  • 32,368
  • 48
  • 194
  • 335
  • 3
    May be restrict `Foo`'s constructor to take a reference to `Fabric` as constructor parameter. – πάντα ῥεῖ Oct 20 '18 at 07:21
  • @πάνταῥεῖ: Hmm, clever! That makes the compiler ensure the construction rules, but still requires manual labor in each sub-class. – Violet Giraffe Oct 20 '18 at 07:22
  • That won't forbid subclasses of `Fabric` to construct any `Foo` instances tho :-/ – πάντα ῥεῖ Oct 20 '18 at 07:24
  • Why do you need it? It's seems like an X-Y problem.. – Coral Kashri Oct 20 '18 at 07:26
  • @πάνταῥεῖ: that's not a requirement. – Violet Giraffe Oct 20 '18 at 07:28
  • @KorelK: this is exactly the problem. I have two sub-classes for which I need to ensure this rule, possibly more in the future, and I'm looking for the best way to express this intent with code in such a way that's enforced by the compiler, and also requires the least duplication in each of the sub-classes. There's no X. – Violet Giraffe Oct 20 '18 at 07:29
  • Hmm, well. Someone could still create a `Fabric` instance, and use that to construct subclasses of `Foo`. I didn't think that through to the end. I once wrote a [Q&A](https://stackoverflow.com/questions/27492132/how-can-i-remove-refactor-a-friend-dependency-declaration-properly) how to remove `friend` relationships properly. I believe that describes that pattern better. – πάντα ῥεῖ Oct 20 '18 at 07:30
  • @πάνταῥεῖ: you're right, but that's also not a requirement. I'm trying to defend against human error in my own code, not against malicious intent in some library intended for wide use. – Violet Giraffe Oct 20 '18 at 07:32
  • @πάνταῥεῖ: Post an answer if you don't have any more ideas? – Violet Giraffe Oct 20 '18 at 07:33

1 Answers1

2

One option is to declare a tag structure which is only constructible by Fabric and pass this object to the constructor of Foo. If you forget to add the constructor to derived classes you will get an error that Foo isn't default constructible.

struct FooTag
{
    friend struct Fabric;
private:
    FooTag();
};

struct Foo {
    Foo(FooTag tag) {}
};

struct FooConcrete1 : Foo {
    using Foo::Foo;
};

struct Fabric
{
    void test()
    {
        FooConcrete1 f = FooConcrete1(FooTag());
    }
};

int main()
{
    FooConcrete1 f; // can't construct as we can't construct FooTag
    return 0;
}
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60