Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack allocated RAII objects vs DI principle

In C++ I often use RAII-style objects to make code more reliable and allocate them on stack to make code more performant (and to avoid bad_alloc).

But creating an object of concrete class on stack violates the dependency inversion (DI) principle and prevents mocking this object.

Consider the following code:

struct IInputStream
{
    virtual vector<BYTE> read(size_t n) = 0;
};

class Connection : public IInputStream
{
public:
    Connection(string address);
    virtual vector<BYTE> read(size_t n) override;
};

struct IBar
{
    virtual void process(IInputStream& stream) = 0;
};

void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        Connection conn(address);
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}

I can test IBar::process, but I also want to test Some::foo, without creating real Connection object.

Surely I can use a factory, but it will significantly complicate code and introduce heap-allocation.
Also, I don't like to add the Connection::open method, I prefer to construct completely initialized and fully functional objects.

I would make Connection type a template parameter for Some (or for foo if extract it as a free function), but I'm not sure that it's right way (templates look like a black magic to many people, so I prefer to use dynamic polymorphism)

like image 242
Abyx Avatar asked Oct 18 '11 12:10

Abyx


3 Answers

What you are doing right now is "force-coupling" the RAII class and the service provider class (which, if you want testability, should really be an interface instead). Address this by:

  1. abstracting Connection into IConnection
  2. have a separate ScopedConnection class that provides RAII on top of that

For example:

void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        ScopedConnection conn(this->pFactory->getConnection());
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}
like image 133
Jon Avatar answered Nov 19 '22 03:11

Jon


By "I can use a factory, but it will significally complicate code and introduce heap-allocation" I meant the following steps:

Create abstract class and derive Connection from it

struct AConnection : IInputStream
{
    virtual ~AConnection() {}
};

Add factory method to Some

class Some
{
.....
protected:
    VIRTUAL_UNDER_TEST AConnection* createConnection(string address);
};

Replace stack-allocated connecton by smart pointer

unique_ptr<AConnection> conn(createConnection(address));
like image 29
Abyx Avatar answered Nov 19 '22 02:11

Abyx


To chose between your actual implementation and the mocked one, you have to inject the actual type that you want to construct in some fashion. The way I'd recommend is injecting the type as an optional template parameter. It allows you to unobtrusively use Some::foo as you used to, but enables you to swap the created connection in case of a test.

template<typename ConnectionT=Connection> // models InputStream
void Some::foo(string address, IBar& bar)
{
    onBeforeConnectionCreated();
    {
        ConnectionT conn(address);
        onConnectionCreated();
        bar.process(conn);
    }
    onConnectionClosed();
}

I would not create the overhead of a factory and runtime polymorphism if you know the actual type at compile time.

like image 2
jupp0r Avatar answered Nov 19 '22 01:11

jupp0r