0

I'm fairly new to many modern C++/OOP concepts and I've got a question about RAII design.

Consider a class that manages and interfaces a transient resource: A TCP socket connection, a connection to a Bluetooth device, and communication with said devices. Things like this. We can assume the class is completely useless if the resource is or becomes unavailable.

I can't decide if RAII (specifically, acquiring/connecting to the resource in the ctor) is good or bad for this type of class.

On the one hand, if the class functionality depends on the resource, it seems to make sense to throw from the ctor rather than on some later Connect() call. On the other hand, acquiring a transient resource typically involves some blocking and/or async operation, which feels like poor constructor design to me.

I've found a few other topics that dance around this question, but haven't fully satisfied my curiosity: RAII, Berkeley Sockets, and STL Containers

RAII for resources that can be invalidated

Any guidance on best-practices will be appreciated!

EDIT: Clarifying my question based on comments below. With the portion of RAII that deals with constructors establishing all class invariants -- a device connection is an invariant because the class should guarantee all public methods that try to communicate with that device are valid after construction. HOWEVER -- can this actually be considered a class invariant since the program has no control over the remote device's availability?

Sean Kelly
  • 338
  • 1
  • 10
  • 2
    My understanding of RAII in C++ is that it should not force you to acquire a resource in a constructor. AFAIK, no `std::` class requires that. You can have an "empty" vector, thread, file stream, smart pointer, etc. (typically a default-constructed instance of a particular class). RAII is more about an automatic release of resources in destructors. Note that even then, resources can be typically released manually prior to destruction by some member functions (e.g., `std::fstream::close`). Which gives you better control of errors, since throwing destructors are evil. – Daniel Langr Nov 04 '18 at 20:39
  • RAII is a guideline. When it makes sense, fully embrace it. If not, keep the scope-bound resource management portions of it. – user4581301 Nov 04 '18 at 20:48
  • Thank you both for your input! Daniel -- I thought one component of RAII was to establish and initialize class invariants in the constructor. I guess my question then becomes 'can transient resources be considered class invariants?' coupled with 'is a blocking connection in a ctor evil?' – Sean Kelly Nov 05 '18 at 10:51

1 Answers1

0

If a resource can have an "empty" state, it's perfectly reasonable for a class owning that resource to have a default constructor (and acquire and release methods).

All the members having to check for non-empty indicates the class can't usefully have an "empty" state, and it shouldn't be DefaultConstructable, and should only be Moveable if it is Copyable.

Typically you would also have a constructor that acquired.

It is ok to async construct something, although that will involve wrapping the construction in an async function.

template <typename T, typename ... Args>
std::future<T> constructAsync(Args&& args...)
{
    return std::async([](Args&& args...) { return T(std::forward<Args>(args)...); }, std::forward<Args>(args)...);
}

int main()
{
    auto foo = constructAsync<Foo>();
    auto bar = constructAsync<Bar>("some", "params");

    foo.get().frob(bar.get()); // only occurs when both are ready
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Doesn't that defeat a big benefit of RAII a little bit? One of the nice aspects of the RAII pattern is to avoid needing to acquire/release and then drop all implementation logic in the other member functions for checking whether acquire/release has been called. Though, if there's no way around it, that's that. – Sean Kelly Nov 07 '18 at 11:31
  • @SeanKelly added some explanation: not all resources have an "empty" state, and that heavily constrains what you can do with the RAII wrapper. – Caleth Nov 07 '18 at 11:37
  • This all makes sense, but the idea of a resource that's acquired at construction but becomes invalidated later on (via external means) sounds like a different topic to me. Unless I'm missing something. – Sean Kelly Nov 09 '18 at 11:01
  • @SeanKelly The classic is a moved-from value. The previous owner releases to the new owner, and can be destroyed without affecting the resource – Caleth Nov 09 '18 at 11:08
  • Oh, I see your point. Sure - I'm not too concerned about destroying the resource that 'goes away' in my case. It's more of a philosophical question: given that RAII provides consumers a guarantee that its resources are acquired, available and initialized after constructing the RAII object, would it violate the spirit of RAII to have a moved-from value (for example) as a member variable of an RAII class? Or is this fine to do? – Sean Kelly Nov 10 '18 at 10:30