6 ms·
This is an interesting write-up, but it leaves out some specifics in designing C++. A few are: 1. Concurrency In garbage collected languages, the runtime can
by humanrebar 10y ago
This is an interesting write-up, but it leaves out some specifics in designing C++. A few are:
1. Concurrency
In garbage collected languages, the runtime can generally add objects to a free list and clean up memory when it's more-or-less convenient. Since C++ uses deterministic destruction and freeing, there has to be clear responsibilities defined. In particular, it has to be clear which thread is ultimately responsible for running the destructor. Point being, writing code that would otherwise be "abstract" in Java-land implies (but rarely explicitly defines!) some rigidity in design with respect to how long objects need to be around for different threads to use them.
Maybe that's too abstract. Here's some code to illustrate:
class IHandler {
public:
virtual Status handleAsync(const Msg & msg) = 0
};
class CopyingHandler : public IHandler {
private:
WorkerThread m_worker;
public:
Status handleAsync(const Msg & msg) override {
// capture the msg by value, taking a copy
return m_worker.push_back([=msg](){
handle(msg);
});
}
};
class RefHandler : public IHandler {
private:
WorkerThread m_worker;
public:
Status handleAsync(const Msg & msg) override {
// Capture the msg by reference. Assumes
// sticks around at least as long as the
// background thread does.
return m_worker.push_back([&msg](){
handle(msg);
});
}
};
Anyway, I left some details out, but you can see that both implementations adhere to the interface provided by IHandler, but each makes drastically different assumptions about what's OK. In some cases, problem domains, copying the message might create a performance bug. In most programs, you could easily (likely) have undefined behavior by capturing the message by reference, letting the message be destroyed, and then trying to use the message afterwards.
Point being, rigidity in C++ designs is easy to create subtly and accidentally, and even generic programming won't necessarily help you here.
2. Polymorphism
C++ actually allows for quite a range of polymorphic behaviors. You can't really rank them from highest rigidity to the least, since there are pros and cons to each approach, but generally inheritance is more rigid than the other approaches. In contrast, you can also have:
2a. Opaque Pointers
Not necessarily very type safe, but if you need to wrap something up in a black box, pass it around, then unwrap it and use it later (maybe in a message queue, a mailbox, a private implementation, or in certain kinds of IOC patterns), you can completely lose your type and recover it later. Typically there is a small performance hit as you use RTTI (https://en.wikipedia.org/wiki/Run-time_type_information https://en.wikipedia.org/wiki/Run-time_type_information) or a discriminator (an enum maybe) to decide how to recover your type when you're ready for it. See <experimental/any> (http://en.cppreference.com/w/cpp/experimental/any http://en.cppreference.com/w/cpp/experimental/any) for an example of this.
2b. Type Erasure
This one is more involved but clever use of generic programming lets us automatically provide the adaptor boilerplate to get types to play nice with other code. Sean Parent does an excellent walkthrough of this technique in his GoingNative talk in 2013 (https://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil https://channel9.msdn.com/Events/GoingNative/2013/Inheritanc...). Point being that type erasure lets you have a different flavor of flexibility in a way not seen in Java-land.
2c. Unions
This is harder to do properly in practice, as evidenced by the difficulty in standardizing something like boost::variant, but from time to time it might be worth playing around with various types of unions. They're a bit awkward, but you might try boost::variant if you like the tradeoffs it provides.
3. Value Semantics
Since objects are values in C++, you need to know the size of things much more often. This causes dependencies you might not think about in other languages, depending on how their standard libraries are designed.
- cbsmith 10y ago> Point being, writing code that would otherwise be "abstract" in Java-land implies (but rarely explicitly defines!) some rigidity in design with respect to how long objects need to be around for different threads to use them. C++ allows you to have much more tightly defined contracts, which tends to be helpful, but you can have very loosely defined contracts (such as with shared ptr). It just turns out that such loose semantics aren't often found to be helpful in the C++ world.
- humanrebar 10y agoMy point was that you can easily stumble into a tight contract (at least in certain respects) in C++. At any rate, it's something that Java-school teachers like Bob Martin don't tend to cover. And I'm not aware of very comprehensive books on C++ design principles.
- cbsmith 10y agoIt may surprise you to know Uncle Bob has written C++ design books as well. ;-) There are lots of books on C++ design that focus particularly on the semantics of contracts that are comparatively unique to C++ (Sutter, Meyers, Alexandrescu, and Abrams all have a pretty established following). Indeed, I'd argue C++ is one of the few languages where you really need to read a lot of the supporting literature before you are much of a useful coder.
- Ace17 10y agoRobert C Martin - Agile Software Development: Principles, Patterns, and Practices There's now an updated version of this book for C# but the first one deals with C++. This book explains, in details, how to architecture your application around use cases. "Game Coding Complete 5" also talks about moderately large scale C++ high-level design. Of course, it's focused on games, but unlike other game programming books, it deals with things like dependency inversion, abstracting away the framework, data-driven architectures, etc. It's surprisingly hard to find books talking about high-level software architecture ; Amazon is flooded with technology-specific books (things like "Learn <insert framework name> in 7 days", whose lifetime is so short, you wonder, why write a book about this in the first place).