Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

force a specific constructor used solely in certain code, nowhere else

Sometimes, we need to provide a specific constructor solely for test usage. How can we force such constructor is solely used in test code, nowhere else. I just wonder if this is achievable in c++11/14. e.g.,

class A {
public: 
  A() = default; // used only in test code
}
class A_Test : public ::testing::Test {
 private:
  A a; // it is ok.
};
class A_Production {
 private:
  A a;  // compiler error
}

I could imagine to use friend decorator and put the specific constructor in protected to limit the access. but there are other existing friends in legacy code too. Is it possible to make a custom specifier like protected in c++1x?

any ideas?

like image 536
pepero Avatar asked Nov 15 '16 13:11

pepero


2 Answers

You could use the Passkey Idiom:

Instead of a direct friendship, you limit the access to A from A_Test through the indirection provided by ConstructorKey, which A uses in its interface where it wants the friends of ConstructorKey to access.

class A {
  class ConstructorKey {
    friend class A_Test;
  private:
    ConstructorKey() {};
    ConstructorKey(ConstructorKey const&) = default;
  };
public: 
  // Whoever can provide a key has access:
  explicit A(ConstructorKey); // Used only in test code
};

class A_Test : public ::testing::Test {
 private:
  A a {ConstructorKey{}}; // OK
};

class A_Production {
 private:
  A a {ConstructorKey{}}; // Compiler error
};
like image 170
Johel Ernesto Guerero Peña Avatar answered Oct 31 '22 02:10

Johel Ernesto Guerero Peña


I can think of several ways to do that.

  1. Make the constructor protected, with only the test subclass using it.

  2. Add a forward declaration to some dummy class, class TestClassThatShouldNotBeUsedInProductionCode;, then declare a constructor that takes a reference to this class as a parameter:

    A::A( /* other constructor arguments */,
          const TestClassThatShouldNotBeUsedInProductionCode &)
    

This constructor can simply ignore this parameter altogether. Your test module can define this dummy, empty class: class TestClassThatShouldNotBeUsedInProductionCode {};, and be able to construct your A class using it. Only your test module would be able to use this constructor, then, and its name makes it pretty clear what the purpose of this is. There isn't really any way to define certain translation units as "real" code versus "test" code, in C++, you just want to implement a clear policy that would be hard to violate accidentally.

Some variations are possible, such as using an inner class instead of forward-declaring a standalone class. The inner class could only be instantiated only by the test code.

like image 44
Sam Varshavchik Avatar answered Oct 31 '22 03:10

Sam Varshavchik