Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Yet another RAII discussion  (Read 14529 times)

0 Members and 1 Guest are viewing this topic.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Yet another RAII discussion
« Reply #15 on: February 12, 2016, 11:29:38 am »
Good that we're back on a meaningful discussion. Thanks for the advice, I see that some parts of my post didn't come across nicely. As mentioned, this topic has come up so often here, that I was annoyed that the same arguments were brought again, ignoring all my (and other's) previous efforts to explain the matter. As I also said, I'm aware that you may not have been part of those discussions though.

So let's get back to topic:

Quote
Now, that article is primarily against shared_ptr, although RAII is mentioned.
Indeed, the article argues against shared_ptr, not RAII. It would be really interesting to see some founded argument against RAII. What I've seen so far are mostly points along "I don't need it", "it's a matter of taste/code style" and "I can program correctly without it". And I can't take them seriously, as they're 100% replacable and generic.

Quote
I do not think that argument extends to insisting smart pointers be used in every single scenario where memory is allocated, a core principle of RAII.  If the allocated memory can be completely handled by the tried and true method of a proper constructor, destructor, and copy/move/assignment/etc operators as necessary, I do not see this as a danger except in the hands of an inept programmer.
But already here, you have to write and maintain more code than with smart pointers. Smart pointers can relieve you from implementing the destructor, and when they're really smart, even the Big Three/Five.

This is what I always encounter: no argument against RAII, just saying that something can be achieved without it. But where's the advantage in abandoning it?
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Yet another RAII discussion
« Reply #16 on: February 12, 2016, 11:45:33 am »
Maybe a simple example. I really wonder how this can be done without smart pointers (I actually do, because I currently don't have an idea that doesn't involve loads of boilerplate code).
class GameObject
{
public:
    explicit GameObject(?);
private:
    sf::Drawable* mDrawable;
}

The user should be able to pass any implementation of a drawable object -- sf::Sprite, sf::Text, sf::Shape, etc. And the object should own the drawable, so it must not depend on the sf::Drawable instance being stored elsewhere.

1. How would you make sure the user can pass anything he likes, and the object takes care of properly deallocating the drawable?

2. How do you signal in the GameObject constructor signature that it takes ownership of the passed object, and doesn't just reference it?

3. How would you make GameObject copyable? This is really tricky, because you need a polymorphic copy (if a sprite is stored, a new sprite must be created, etc.). Obviously, the sf::Drawable class hierarchy can't be changed for a clone() method.
« Last Edit: February 12, 2016, 11:48:07 am by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re: Yet another RAII discussion
« Reply #17 on: February 12, 2016, 12:32:53 pm »
Cool.

Indeed, the article argues against shared_ptr, not RAII. It would be really interesting to see some founded argument against RAII. What I've seen so far are mostly points along "I don't need it", "it's a matter of taste/code style" and "I can program correctly without it". And I can't take them seriously, as they're 100% replacable and generic.

One enlightening part of this discussion is that you are ok with using raw pointers for non-owning pointers, as we discussed earlier.  That would largely remove the necessity for shared_ptr, which is one of my primary technical objections to the use of RAII.  Out of curiosity, would you describe this as the dominant view of RAII advocates?  I would have thought not. 

For example, in the Wiki for RAII, shared_ptr is explicitly mentioned as follows.

Quote
Ownership of dynamically allocated objects (memory allocated with new in C++) can also be controlled with RAII, such that the object is released when the RAII (stack-based) object is destroyed. For this purpose, the C++11 standard library defines the smart pointer classes std::unique_ptr for single-owned objects and std::shared_ptr for objects with shared ownership. Similar classes are also available through std::auto_ptr in C++98, and boost::shared_ptr in the Boost libraries.

I wanted to check out how you implement RAII in practice.  So I checked out the Thor source code.  I'm not doing this to attack you.  Just simply to see and understand your approach to RAII.

I do notice in Thor, you do seem to stick largely to smart pointers.

If I search the Thor source code for shared_ptr vs. unique_ptr, you do make use of a lot of shared_ptr.  I think I found it in at least equal measure to unique_ptr, based on a rough count of the search results.

So, if RAII does advocate the use of shared_ptr (which I think it commonly does), and your implementation of RAII uses shared_ptr (which it appears to), then the article I posted critiquing shared_ptr is also a critique of RAII, as it employs shared_ptr.

I think that leaves us here.  Either:

1. You accept shared_ptr as part of RAII, in which case we should debate the merits of the article I linked critiquing shared_ptr.

Or,
2. You do not accept shared_ptr as part of RAII.  This would appear to be counter to your code, but I accept you may have changed your stance on things.  If so, this changes the debate considerably.

Is that fair?

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re: Yet another RAII discussion
« Reply #18 on: February 12, 2016, 12:47:37 pm »
class GameObject
{
public:
    explicit GameObject(?);
private:
    sf::Drawable* mDrawable;
}

My approach would be somthing like this:
(pseudo-code, didn't compile).

enum DrawableType
{
   Sprite,
   VertexArray,
   CircleShape,
   RectanbleShape,
   // ...
};

class GameObject
{
public:
   GameObject(DrawableType type);

private:
   sf::Drawable* mDrawable;
   DrawableType mType;
};


GameObject::GameObject(DrawableType type)
{
   mType = type;
   switch (type)
   {
      case Sprite:
         mDrawable = new sf::Sprite;
         break;

      // etc
   }
}

GameObject::GameObject( const GameObject& rhs)
{
   switch (mType)
   {
      case Sprite:
         mDrawable = new sf::Sprite;
         *mDrawable = *rhs.mDrawable;
         break;

      // etc
   }
}

// ... similar code to above for other operators like assignment ...

GameObject::~GameObject
{
   delete mDrawable;
}
 

There is no question of ownership, as the pointer only exists within GameObject, and is allocated in the constructors.

What would your implementation be?

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re: Yet another RAII discussion
« Reply #19 on: February 12, 2016, 01:08:18 pm »
I should add -
my implementation above does not have an API to configure the sf::Drawable*.  Obviously you'll want to do that.

I would probably have some sort of GameObjectManager class, with a series of Create* functions that allow various parameters with which you can configure the Drawable*.  Different functions and parameters may be required for different Drawable subtypes.

Alternatively, if you need to configure the sf::Drawable* past GameObject construction, and you need to access specific functions of the derived class (e.g. sf::VertexArray::append), then a down-cast from sf::Drawable* is inevitable.  I believe this would be true in any other proposed implementation as well.  Although the DrawableType data member should allow you to safely do this with a static_cast rather than a dynamic_cast.
______

Now, here's how I actually do it in my code.

I primarily make use of sf::VertexArray, and sf::Sprite.  I don't use most other drawables.  I use separate GUI middleware, for example, so sf::Text is unnecessary.

I actually have 2 separate classes,
class ManagedSprite
and
class ManagedVertexArray

These classes create an sf::Sprite and sf::VertexArray in their constructor, and store this as a pointer data member.  It is copied and destroyed through all the usual constructor, operator, and destructor methods.  They also contain a data member which specifies their draw order in the game. 

These classes are owned by a manager.  I call it DrawManager.  The ManagedSprite are stored in one std::vector.  The ManagedVertexArray are stored in another.  They can be looked up by a ManagedSpriteID and ManagedVertexArrayID, which corresponds to indices in these std::vectors.  Game entities (actually the Drawable component of game entities) store ManagedSpriteIDs and ManagedVertexArrayIDs only, and never a pointer.  So, it's your typical "handle" implementation.  But the handle can be used to lookup and modify these drawables as needed.

The DrawManager maintains a separate list of ManagedSpriteID and ManagedVertexArrayID in draw order.  This is how I know which drawables need to be drawn overtop of which others.

The separate draw order list is necessary so that I never have to reorder the vectors storing ManagedSprites and ManagedVertexArrays.  As this would invalidate the IDs stored by components, which are indices into these arrays.

The point of all this is that I don't actually have any kind of GameObject which requires a polymorphic pointer to an sf::Drawable.

Some advantages to this approach are:
  • no downcasting is ever necessary
  • clear ownership of memory by the manager and the managed classes it employs(this is what managers do)
  • the DrawManager's std::vectors contain sequentially ordered data, not pointers to data, avoiding an extra level of indirection which thrashes the memory cache and slows the game update loop.  This is a common paradigm I use, largely borrowed from data-oriented design, which does have some strong principles for perforance
  • pointer safety, as no persistent pointers to any dynamically allocated memory (sf::sprites, sf::VertexArrays, ManagedSprites, ManagedVertexArrays) is employed outside of the manager.  Everything else uses handles.
  • an easy implementation of draw ordering
« Last Edit: February 12, 2016, 01:35:01 pm by Jabberwocky »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Yet another RAII discussion
« Reply #20 on: February 12, 2016, 01:28:30 pm »
I think we shouldn't mix RAII and shared_ptr. RAII is really only the basic idiom (Resource Acquisition is Initialization, or more appropriately, RRID = Resource Release Is Destruction). The main idea is that lifetimes of a resource is bound to the lifetime of an object, and because objects are deterministically destroyed in C++, so are their bound resources.

shared_ptr combines this with several additional features:
  • Reference counting: The last one turns off the light. As a user I don't need to know who is last.
  • Strong and weak reference semantics: shared_ptr instances keep an object alive, while weak_ptr instances only reference it, but in a safe way (one can check whether the object still exists)
  • Dynamic deleters: a function that deallocates the object can be passed to the constructor, and is type-erased (isn't part of the shared_ptr<T> type).
  • Thread safety: the reference-counting aspect of shared pointers works concurrently.
The reason why shared_ptr is always mentioned together with RAII is because it was the first popular smart pointer. In C++98, boost::shared_ptr was very widely used, and many conceived it as a great general-purpose solution. I'm sad that Boost advertises it to this day to be suited for the PImpl idiom and other scenarios where std::unique_ptr would suffice.

You're right that I use it in Thor. In some cases (thor::ResourceHolder), it's to do exactly what its original purpose is: shared ownership. A user can load several resources, but doesn't know if the holder outlives them, so he can make sure that every resource is kept alive as long as it's in use. In combination with a custom deleter, it's furthermore possible to automatically release a resource when the last reference goes. In other scenarios (like thor::Connection), I need a safe way to check whether an object has already been deleted (disconnected), and shared_ptr + weak_ptr basically gives me that for free. So I did not have to implement my own tracking system to know when an object goes out of scope, but it would certainly have been possible.

Since Thor is a library and not an executable project, the code is generally a bit more generic and tailored to support different use cases (you'll find many templates, for example -- more than I typically have in my code). I'm wondering how many Thor users actually benefit from those shared pointers, though.



My take would be:
class GameObject
{
public:
    explicit GameObject(aurora::CopiedPtr<sf::Drawable> ptr)
    : mDrawable(std::move(ptr))
    {
    }

private:
    aurora::CopiedPtr<sf::Drawable> mDrawable;
};

That's all.

Usage:
// Instantiation:
auto sprite = aurora::makeCopied<sf::Sprite>();
sprite->setPosition(...);
sprite->setTexture(...);

GameObject obj( std::move(sprite) );

// Deep copy:
GameObject copy = obj;
// copy and obj now contain two independent sprites
// if you change the initialization to sf::Text, it will work too

That's really everything. aurora::CopiedPtr takes care of copying, assigning and destroying the internal object. And it knows whether it stores a sprite, shape or text. There's no single case differentiation, and I do not need to implement copy/move constructor, assignment operator, or destructor. The code is extensible as one need not know the classes in advance, works without a type enum, and supports any sf::Drawable (not just predefined ones). The GameObject.cpp does not need to include the headers for sf::Sprite, sf::Text, etc, and is thus only dependent on the base class sf::Drawable.

And most importantly, the code is extremely short and easy to maintain, in my opinion.

Again, this is more about aurora::CopiedPtr being smart, rather than RAII, but it shows what smart pointers can achieve. I find it amazing that this idiom is so unknown in the C++ community, and it seems to be accepted practice to implement The Big Five even though it's often unnecessary.
« Last Edit: February 12, 2016, 01:30:46 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re: Yet another RAII discussion
« Reply #21 on: February 12, 2016, 02:18:39 pm »
That aurora::CopiedPtr looks pretty cool! 

The only objection I might have is "the principle of least astonishment"

You're obviously straying pretty far off the code people expect to see.  If you were to argue, "it's worth it, look how much less code I have to type!  That makes everything safer, too".  I'd say that is a good argument.

If someone else were to argue, "Listen man, I'd prefer to type more code than have some weird templated smart pointer black magic obscuring all my logic, and changing the established way C++ is written", I'd call that a good argument, too. 

This may again be an instance where I would be fine with either approach, whereas perhaps you might view the second argument as inadequate.  But I don't want to put words in your mouth.

Either way, nice work with the masterful smart pointer wizardry.

Onto RAII:

RAII as a principle I have no problem with. 

I'll leave that statement by itself, with italics for added impact.  ;)  We can base the rest of our discussion with that agreement in place.  All my arguments against RAII throughout have been related to the RAII implementation in C++.

If you don't want to discuss RAII implementation in C++, no problem.  We can stop now. 

For me, as a programmer, I would never accept the use of a programming technique without considering the implementation.  For example, I like OOP better than DOD, in prinicple.  But in practice, the implementation of OOP often has terrible performance.  My code usually ends up being a marriage of the two, focusing more on DOD principles where performance is critical.

Similarly, if unique_ptr had a 500% performance hit on dereferencing, nobody would use it.  Or, I certainly wouldn't.  That's not a principle thing, that's an implementation thing.  As you rightfully pointed out, it doesn't.  The implementation is important.

shared_ptr is also an implementation detail.  But there are legitimate concerns with the use of shared_ptr.  The advantages you list for shared_ptr are also cause for some significant concern.  These are highlighted in the article I linked.  It actually has a good section on resource manager-style use of shared_ptr, such as in your Thor implementation.

Thanks for sharing your thoughts on Thor.  Everything makes sense. 

A user of Thor is still choosing to use shared_ptr.  I would say that this decision is one that be made carefully, in light of both advantages and concerns with shared_ptr.  Again, I would argue there are valid arguments to be made both ways.  You may personally weight the evidence, and decide the pros may outweigh the cons.  I would hope that you at least recognize their are cons to be considered.  Again, these are highlighted in the article.

If you read it, and say, "I read the article, and found no cons to using smart_ptr", then I can try to point you in the right direction.

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re: Yet another RAII discussion
« Reply #22 on: February 12, 2016, 03:16:28 pm »
I should add that this article is written by the Lead Software Engineer at Wargaming Seattle, with an extensive history in the AAA gaming industry.  He also mentions consulting his peers in the industry on the topic. 

That doesn't mean he's necessarily correct.  I disagree with people in the industry all the time.  But it does mean he isn't some hack spouting off crap with no experience to back it up, as may have been the case with other articles you've dismissed.

In particular, I would draw your attention to these sections in his article:
  • Debugging References
  • Hidden Performance Issues
  • External Reference Counts (although he backs of this critique somewhat based on feedback)
  • Reference Bouncing
  • Atomics Overhead
  • More on Ownership

... but ideally you'll want to read the whole thing.

Here's a link to the article again.
Dangers of std::shared_ptr

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Yet another RAII discussion
« Reply #23 on: February 13, 2016, 09:24:37 pm »
If someone else were to argue, "Listen man, I'd prefer to type more code than have some weird templated smart pointer black magic obscuring all my logic, and changing the established way C++ is written", I'd call that a good argument, too.
Generally I agree that more verbose approaches can make sense when they're simpler and less obscure (like it is the case with many Boost examples). In this case however, I'd say the abstraction of copyable smart pointers is really easy to understand. It isn't filled with surprises, as the pointer has value semantics and thus behaves exactly as one is used from C++ objects. If you say: "look, CopiedPtr<T> behaves exactly like T, except that it has some advantages of a pointer, like cheap move construction and ability to deal with polymorphic types", the code should be pretty straightforward to understand. One does not need to know how CopiedPtr<T> is implemented.

Of course, people who have never seen aurora::CopiedPtr might wonder what it's doing, but that can be amended by looking up its documentation or even just by a one-liner comment in the place it's used. Reading and understanding equivalent boilerplate code, including a separate enum type, may arguably take more time and could lead to questions.

Either way, nice work with the masterful smart pointer wizardry.
Thank you. In my opinion, copied pointers are definitely the most versatile and useful tool in the Aurora library; I've used them in loads of places already. It took me some iterations to get to the current design though, early versions were quite limited and less optimized.

But there are legitimate concerns with the use of shared_ptr. [...] I would hope that you at least recognize their are cons to be considered.
I think you misunderstood my opinion in this regard. I definitely agree that shared pointers have disadvantages and their use cases have to be considered carefully. I've also noted this in my article and mentioned here that I consider it overused. Still, the article you linked is definitely interesting to read, as it outlines some lesser known problems in detail.

So, it seems like we agree that RAII can greatly simplify and improve code, and abandoning it is primarily reasonable due to problems with concrete implementations, such as std::shared_ptr? :)
(and even in these cases, I'd argue that a different implementation can be more suitable than MMM...)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Jabberwocky

  • Full Member
  • ***
  • Posts: 157
    • View Profile
Re: Yet another RAII discussion
« Reply #24 on: February 14, 2016, 11:26:18 am »
re:  aurora::CopiedPtr

Yep, good arguments.

There's no doubt that having to write some/all of a constructor, destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator gets pretty burdensome.  I am highly sympathetic to simplifying this.

I recall reading an article (or maybe it was a video) by Jonathan Blow, who is a pretty popular and influential indie game developer.  He discussed the need for a new programming language for games.  He has many issues with C++ but these complexities addressed by aurora are one of them.  So it's not an insignificant problem.  It legitimately pushes some smart people away from C++.  Blow is actually working on a new language for games.  That link is off-topic, highly optional reading, but just figured I'd include it for those interested. 

Personally, I'd have to be dragged kicking and screaming from C++.  But I get people's complaints.  I would actually be interested in official C++ tackling this problem, similar to how your aurora stuff does.  If it were part of the standard, the "principle of least astonishment" argument (whether you agree with it or not) would completely disappear.

re:  C++ RAII

I definitely agree that shared pointers have disadvantages and their use cases have to be considered carefully. I've also noted this in my article and mentioned here that I consider it overused. Still, the article you linked is definitely interesting to read, as it outlines some lesser known problems in detail.

Cool.  Thanks.

So, it seems like we agree that RAII can greatly simplify and improve code, and abandoning it is primarily reasonable due to problems with concrete implementations, such as std::shared_ptr? :)

Yep.

This is where I am at.  If I liked the concrete implementation, I would try adapting RAII in my code.  I can't guarantee I wouldn't find other issues once I got my hands dirty.  But I would be sold on giving it a shot. 

_____________

Here's the broader picture.  There's a series of mental steps a person has to make to go from traditional manual mem management, to adapting RAII. 

1.  Syntax
It is very common to dislike the syntax, which I readily admit is a very minor issue.  But hey, coders will argue for hours on where to put curly braces.  So while I might mention it, I wouldn't pursue it as a serious argument against RAII.  But the first reaction is often an "ewww" one.  ;)

I'm just talking about the thought process, not trying to make arguments to debate here.

2.  Old vs. New
It's very common to require convincing, by oneself or others, that RAII is actually better, and if so, by how much.  Memory management and memory leaks are issues programmers have dealt with for a long time.  We've written a lot of software using our old, non-RAII tools and techniques.  We are competent with them.  We trust them.  They have worked well for many of us in the past.  Arguments telling us that they don't work just annoy us, because we believe they do.  It would be like someone telling you you should use C# because you can't trust programmers to work with memory, with or without RAII (both of which are susceptible to programming errors, although RAII less so).

This is why people will mention things like memory leak detectors.  I readily accept that there are reasonable arguments against these older techniques.  But obviously, the argument for RAII would be far stronger if these older techniques did not exist.  That's why they get brought up.  RAII is a replacement to existing techniques.  So you must examine both together.  It's a debate between the old and the new.  Personally, I believe it is wrong to frame the memory management argument as "the wrong way" and "the right way" (and doing so will likely alienate those who require convincing).  Rather, I believe it should be framed as the advantages and disadvantages of different tools.  Both advantages and disadvantages exist.  "Familiarity", "existing, functional code", and "established technique" being some arguments for manual management. 

For example, I chose to use CEGUI for my UI library in my current game.  There are a lot of other GUI choices.  Maybe some of them are even better.  I don't know.  But I have a huge familiarity with CEGUI.  I have a lot of existing code.  I have a highly efficient workflow.  I just simply know how it works.  So it's one less unknown piece of tech, one less big learning curve I have to deal with in my current game.

Maybe some other GUI libs do things a little better, in certain ways.  That alone would not be enough for me to change.  It would have to be a lot better.

I'm sure one day I will change to something other than C++.  Some fancy new language.  But when that day comes, it will take a lot of convincing.  I'm good with C++.  It works.  Same kind of thinking.

Again, not trying to make arguments.  Discussing thought process.  ;)

3.  Examining reasons not to adapt RAII.
This is where we hit the implementation, and smart pointer issues. 

As discussed above, many programmers have to be strongly convinced to abandon tried and true techniques for something new.  This is reasonable. 

I'm pretty good with my existing techniques.  Changing requires me to abandon those.  Learn new stuff.  Rework my existing code.  Likely deal with new kinds of bugs and other issues as I adapt and learn.  Take for example the difficulties associated with debugging non-released memory from shared pointers (memory I expect to have been released).  That's a different kind of debugging than I've ever had to do before.  I'm starting from scratch.

So, if I see definite problems in the implementation of C++ RAII, that may be enough to weaken the "strong convincing" I need to adapt it. 

That's why discussing these problems associated with shared_ptr are such an important part of the process.  Or, instead, discussing alternatives to shared_ptr that do not have these problems.

This is why I was so interested earlier in talking about shared_ptr alternatives, using a combination of unique_ptr and raw pointers.

That's a big wall of text.  But I hope what it achieves is to be more sympathetic to those who either decide not to adapt C++ RAII, or are somewhere along the stages of accepting it, but not quite there yet.

Thanks for the great discussion, Nexus.
« Last Edit: February 14, 2016, 11:46:16 am by Jabberwocky »

Mörkö

  • Jr. Member
  • **
  • Posts: 96
    • View Profile
Re: Yet another RAII discussion
« Reply #25 on: February 14, 2016, 07:23:17 pm »
There are enough messy memory leaking C++ programs, and enough perfectly leak free C programs and RAII-less C++ programs to prove that smart pointers are not the definite solution to this set of problems.

And I suppose I have to mention the obvious: There are enough of the vice-versa case too that there is no question whether memory leaks are a problem for all levels of programmer. The C++ committee tried to fix it but the solution is nowhere near perfect and as has been shown to create its own new set of issues. It's up to everyone to decide whether the benefits of RAII outweigh the introduced downsides and there is no clear answer to that question as evidenced by the mere fact of debates of this nature.

Ultimately I think the issue is more of taste and style than of actual real practical importance. In my opinion, if you are going to use C++ then you might as well go all the way with templates, classes, RAII, OO paradigm etc. C++ is sold as being multi-paradigm, which might be technically true, but at the same time the language is hinting, implying, and seductively inviting you to write OO code. I'd say that limiting oneself to only a subset of C++ as some people advocate is a bad idea, because it's designed so that if you use one of the "nice" features then eventually you will want (need) to use all of them anyway and trying to resist that process will make your code a mess. If you don't want to use everything in C++, then just use C!

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Yet another RAII discussion
« Reply #26 on: February 15, 2016, 12:59:02 pm »
I recall reading an article (or maybe it was a video) by Jonathan Blow, who is a pretty popular and influential indie game developer.  He discussed the need for a new programming language for games.
Seems quite interesting, thanks for the resources!

Personally, I'd have to be dragged kicking and screaming from C++.  But I get people's complaints.  I would actually be interested in official C++ tackling this problem, similar to how your aurora stuff does.  If it were part of the standard, the "principle of least astonishment" argument (whether you agree with it or not) would completely disappear.

2.  Old vs. New
Yep, that's definitely a good point. You also see how C or Java programmers struggle to get rid of their habits when they learn C++. It's probably a quite common phenomenon that people don't want to leave the comfort zone as long as things work. But it's often worth the step, because it's an opportunity to get in touch with really cool new stuff. It also depends on the personality; some people are happy when things work and invest 100% their time into productive code, and others try to use every opportunity to learn new things. I think it helps greatly when one is interested in programming per se, not just the results thereof.

I think it's also one of the main reasons why C++ is still that popular today. There are many alternatives, and arguably languages with a similar application field that do several things better, because they don't have to carry all the historical burdens (D as an example). Yet, they apparently don't provide advantages crucial enough to justify a re-learning of an entire language, migration of code bases, rewrite of libraries or switch of communities.

Which is also a reason why I see potential in libraries like Aurora, Ponder, range-v3 and other specialized language-level C++11/14 libraries. (I don't know that many yet). While not providing 100% the support of native language features, they make C++ much more comfortable to work with. Boost also contains many ingenious ideas, with a modernized and decoupled code base it would be really cool to work with.

Thanks for the great discussion, Nexus.
The same to you. It's amazing how this turned out :)

enough perfectly leak free C programs and RAII-less C++ programs to prove that smart pointers are not the definite solution to this set of problems.
That's not a proof, and it misses the point. We've discussed this in great detail already, you might want to read the thread carefully ;)

The C++ committee tried to fix it but the solution is nowhere near perfect and as has been shown to create its own new set of issues. It's up to everyone to decide whether the benefits of RAII outweigh the introduced downsides and there is no clear answer to that question as evidenced by the mere fact of debates of this nature.
That's a very vague paragraph without explanation. What RAII downsides? What "own set of issues"? (Remember we're talking about RAII itself, not shared_ptr or other undoubtedly problematic implementations).

Also, many people agree that C++ has the best resource management strategy in its family of programming languages. Java's resource handling for example is terrible. Yes, it has a GC, but that only serves memory. Every other resource must essentially be closed manually, like in the old days of C (finalizers are not reliable and thus effectively useless for resource deallocation). Things have become slightly less terrible with try-with-resources, but this idiom is still miles behind RAII, because it is limited to local variables and requires boilerplate code at call site (instead of inside the resource, i.e. destructors). And since exceptions are prevalent in Java (and try-catch clutters the code base, thanks to checked exceptions), exception safety is even more important. And without RAII, it's a mess. The try-finally approach is extremely tedious, because you must rewrite the rollback logic every time you use a resource, instead of once in the resource itself.

Deterministic scope-bound destruction is a remarkably effective concept. Languages like D have recognized this and even brought it a step further with the scope keyword. And use cases of RAII are not limited to resource management; it can be extended to any ownership-based attach/detach semantics (e.g. when this game character dies, automatically unregister it in other places, through a tracker object).

I'd say that limiting oneself to only a subset of C++ as some people advocate is a bad idea, because it's designed so that if you use one of the "nice" features then eventually you will want (need) to use all of them anyway and trying to resist that process will make your code a mess. If you don't want to use everything in C++, then just use C!
I disagree. Many features of C++ are obviously flawed, often because of their historical origins that don't fit well with modern paradigms. Some have even been deprecated or entirely removed from the standard. Because C++ is extremely complex, it's almost impossible to use every single feature; I find it much more reasonable to carefully choose and combine the well-designed features, rather than forcefully mixing everything the language has to offer into one big pot.

Ultimately I think the issue is more of taste and style than of actual real practical importance.
I find it important to have great discussions that allow developers to investigate new aspects of programming and become more efficient. Profound reasoning being denied by the "taste" compromise argument makes me sad.

Please have a look at my aurora::CopiedPtr example above and tell me how it has no practical importance whether I write 15 lines of code, or 300 while still being more constrained.
« Last Edit: February 15, 2016, 01:01:38 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11030
    • View Profile
    • development blog
    • Email
Re: Yet another RAII discussion
« Reply #27 on: February 15, 2016, 02:14:31 pm »
What I feel is kind of missing from the whole discussion is the clear distinction between RAII and smart pointers.

RAII is quite a bit older than smart pointers and it simply means that resources are bound to an object and will get properly deallocated at destruction of the object. So given what Jabberwocky kind of mentioned in previous posts what he's doing with his self-knitted classes with destructors and copy constructors IS in fact doing RAII - allocate the object in the constructor, deallocate it in the destructor - but it introduces higher risks of getting something wrong (deep copy, proper deallocation) and usually generates more boilerplate code.

This is where smart pointers come in. unique_ptr can essentially be seen with the main purpose of encapsulating a resource for proper RAII handling. It has pretty much always no overhead and prevents quite a few of the risks of writing the boilerplate code yourself. shared_ptr and others do come with some overhead (and potential other issues), but their main purpose is not to provide an RAII encapsulation, as such the argument one seems to hear a lot around the internet of "shared_ptrs have an overhead (and potential other issues) thus RAII is bad" is just plain wrong.


One of the point I think why Nexus, I and others kind of get annoyed whenever a new RAII discussion comes up is, that the topic is not looked at in a more subjective way, while trying to give advice to other people.

You are used to a certain way of coding, that is fine and you can keep doing so, however is your way of coding really the least risky way and the easiest to deal with? As a top athlete, is the workout routine you've figured out over multiple years really the best workout routine for someone just starting out as an athlete? Should one always start with the (arguably) max performance/high risk situation or could other ways fit someone else better? What do experts on this field advice?

My point being: When giving advice to other people, you shouldn't assume everyone is as "clever" as you are and not every has the need for these 2ms performance gain (if any). Instead you should give advice on things that have low risk of using it wrong and are easy to maintain.


Disclaimer: The "you" here is used in a general sense and not specific to anyone.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/