• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
johnmarinelli

Rule of Three, and const-correctness questions

39 posts in this topic

Ive met a few programmers but I never heard of Rule of Three.

Using consts makes more sense. Yes, it should be Update( const int deltaTicks ), ....but no big deal if you dont use const, but its highly recommented if other people are working on that code too. And you dont have to pass an int by reference when you want it to be const.

0

Share this post


Link to post
Share on other sites

Ive met a few programmers but I never heard of Rule of Three.

Using consts makes more sense. Yes, it should be Update( const int deltaTicks ), ....but no big deal if you dont use const, but its highly recommented if other people are working on that code too. And you dont have to pass an int by reference when you want it to be const.

 

Using const in the Update() function like that is useless in this case. The int parameter is being passed by value, not by reference. By value doesn't need a const qualifier because the value is copied, not referenced. You only need a const qualifier if you're passing by reference/pointer and you don't want the function to change the object being passed.

 

So to answer the OP's last question directly, no. You're not passing by reference there, you're passing by value so the const qualifier is useless.

Edited by leeor_net
2

Share this post


Link to post
Share on other sites

This does become an issue however when working with references. Try to find out for yourself what could happen to the arguments of type Bar in the following methods:

 

Well, it looks like in the first example, b won't get changed where in the second example it could get changed.  I guess I'm overthinking this const-correctness thing smile.png

However, I (think) I still don't understand the uses of the copy ctor/assignment op;

class Foo{
private:
    int data
public:
/*normal ctor*/
    Foo(int d) : data(d) {};
/*copy ctor*/
    Foo(const &Foo copy) : data(copy.data){};
/*assignment op*/
    Foo& operator=(const &Foo copy){ data = copy.data };
};

is the above correct? 

if so, what do they have to do with dynamically allocated memory?

Foo *_ptr = new Foo();

/*does this create a whole new space in memory for test?
  if it does, am I correct in saying that the default assign op
  only makes test point to the same area of memory as _ptr - hence the whole
  deal about copy assignment operators?*/
Foo test = *_ptr;
Edited by johnmarinelli
0

Share this post


Link to post
Share on other sites

Ive met a few programmers but I never heard of Rule of Three.

Using consts makes more sense. Yes, it should be Update( const int deltaTicks ), ....but no big deal if you dont use const, but its highly recommented if other people are working on that code too. And you dont have to pass an int by reference when you want it to be const.

 

Using const in the Update() function like that is useless in this case. The int parameter is being passed by value, not by reference. By value doesn't need a const qualifier because the value is copied, not referenced. You only need a const qualifier if you're passing by reference/pointer and you don't want the function to change the object being passed.

 

So to answer the OP's last question directly, no. You're not passing by reference there, you're passing by value so the const qualifier is useless.

 

"Using const in the Update() function like that is useless in this case"

...well, he said its a reference:), I thought he just forgot the &

 

"You only need a const qualifier if you're passing by reference/pointer and you don't want the function to change the object being passed."

...remove the word only from this sentence and it will be correct.

0

Share this post


Link to post
Share on other sites

You only need a const qualifier if you're passing by reference/pointer and you don't want the function to change the object being passed.


In addition to documenting your intent, const does have a function in this case -- it prevents you from modifying the argument inside the function body. I know it might seem useless to enforce rules on yourself, but this kind of defensive coding helps you to not make trivial mistakes, and prevent future code maintainers from violating your assumptions -- e.g. if your implementation assumes the argument to be constant, but you don't mark it as such, and someone comes along later to fix a bug and modifies it.

I would, and in fact do, go so far as to pass all arguments as const by default, regardless of whether they are by value or by reference. In most cases I would even go one further and take a local copy of that const parameter (in the smallest possible scope) if I wanted to modify it. One place I *don't* do this is when I mean to enable the RVO (the Return Value Optimization), as I do when implementing operator+ in terms of operator+= in my classes. Edited by Ravyne
1

Share this post


Link to post
Share on other sites

However, I (think) I still don't understand the uses of the copy ctor/assignment op;

class Foo{
private:
    int data
public:
/*normal ctor*/
    Foo(int d) : data(d) {};
/*copy ctor*/
    Foo(const &Foo copy) : data(copy.data){};
/*assignment op*/
    Foo& operator=(const &Foo copy){ data = copy.data };
};

is the above correct?

 

It's bad style, because the default copy constructor and assignment operator do the same thing. Typically the reason you need to overload these functions is when you are writing a class that manages a resource, or is a container.

 

 

Also, for managing resources, you can sometimes avoid writing the rule of three functions, by useing a smart pointer, like unique_ptr with a custom deleter.

Edited by King Mir
0

Share this post


Link to post
Share on other sites

Since const correctness still comes across as something that's "nice but not mandatory", let's look at things that will fail if you don't write const correct code.

 

First, the one thing that can really get me mad: people writing interfaces/APIs using char* instead of const char*.

void parseString(char* str) { ... }
 
std::string text;
parseString(text.c_str()); //Fail, as c_str() returns const char*

 

 

While these have a simple workaround, it can still be annoying

void analyseObject(MyClass& obj) { ... }
 
analyseObject( MyClass(a,b,c) ); //Fail, temporary variables are r-values and const

 

struct MyClass {
   void print() {...}
}
 
MyClass(a,b,c).print(); //Fail, temporary = const

 

 

To clarify the observable effects of ignoring the Rule of Three, because unlike missing const correctness, this won't conveniently fail to compile in the first place:

 

struct MyClass
{
    int* data;
    MyClass() : data(new int) {}
    ~MyClass() { delete data; }
};
 
 
vector<MyClass> v;
v.push_back(MyClass()); //Fuse is lit
cout << v.front().data; //Fail
v.clear(); //Fail
 
void function(MyClass a) { ... }
 

 

Let's ignore compilers optimizing out the temp variable or using move semantics, since you can't rely on it.

 

First example creates a temporary instance, then creates a copy in the vector (which just stupidly copies the pointer). The temporary goes out of scope and deletes data. The next line accesses data that was just deleted. You will see either garbage (kind of helpful), crash with an access violation (very helpful) or it will just seem to "work fine" (dangerous), as the memory will most likely not have been overwritten yet.

 

Then we clear the vector and delete the instance in it. Now data is deleted a second time. If you are lucky, your application will crash.

 

 

The second example looks harmless, but does pretty much the same. To call the function, you create a copy. When the function returns, this copy is destroyed and deletes your data. The original object is now in an invalid state with data pointing to deleted data. Any access will hopefully crash and when the original object goes out of scope, the memory is attempted to be deleted a second time.

 

 

Why is crashing the best case scenario? Because it is hard to miss and clearly tells you there is a bug. The same bug could result in working "just fine" most of the time, but returning strange values on every odd Thursday during full moon. This can cost you days or weeks just trying to reproduce the bug and tracking it down.

Edited by Trienco
1

Share this post


Link to post
Share on other sites

Hodgman, on 06 Feb 2013 - 15:44, said:
The rule is: if you have a copy-ctor, assignment-op or destructor, you should have all three of them.

I'd make a few minor but important alterations to that wording:
"if you NEED a copy-ctor, assignment-op or destructor, you should PROBABLY have all three of them."

The addition of the word "need" is because it is entirely possible that you have one or more of the three implemented, but in fact do not need any of them. Post #7 is a great example of this. As the default auto-generated methods already do the right thing, implementing them yourself is an extra unnecessary source of potential bugs, and is likely less-efficient. It's really the wrong thing to do. You simply don't need either the copy-constructor and/or assignment operator in this case.
I've come across many bugs that resulted from an assignment operator that was always completely unnecessary, and because it wasn't updated when another member variable was added to the class, it caused a bug. If it was left to the compiler to auto-generate then the bug would never have existed.

Secondly, the addition of "probably". Whilst this does seem to lessen the strength of the statement, in practice it's a very strong "probably". There are very rare cases where one might need one or two of them but not all three, but you won't run into those until well after you've got the rule of three completely nailed. I wont bother to explain such cases because when you're experienced enough you WILL know them, and it is only then that you should care about them.
Also (and this was meant to be my main reason), whilst it is still typically "wrong" to implement one but not all three of them, often there is another way of following the rule, and that is by explicitly disabling the copy-constructor and/or assignment operator by making them private and leaving them unimplemented. Or it can be done by deriving from a non-copyable class, or having a reference as a member...
Basically so long as the class either: can be copied and will do so correctly, or it CANNOT be copied, then it is alright.
1

Share this post


Link to post
Share on other sites

I have to agree that the rule should be the Rule of Two. If you need a unique assignment or copy ctor, then you really need both of them. If you have smart pointers (and you should, instead of raw pointers) or const members, the autogenerated destructor does the job right, but the assignment and copy ctor need to be custom.

1

Share this post


Link to post
Share on other sites

The rule of two really just underscores that each object should manage at most one resource. If you need an object to manage more than one resource, then put each resource in it's own wrapper. But for the object managing a single resource, you need the rule of three. Or a smart pointer with the right copy semantics.

 

So I would say no to the rule of two.

0

Share this post


Link to post
Share on other sites
Steps to writing const-correct code:
 
1. Understand what const variables and const references & pointers actually mean and how they behave at compile time. Read about it. Make a sample program and test what happens when you try to do various things.
 
2. Understand what a const member function is. As above...
 
3. In your actual code, when you write a new function or member function attempt to make all of its parameters const, especially pointers and refences. Deal with the syntactic fallout.
 
4. Where 3. turned out to be impossible realize that those parameters can't be const.
 
5. Do this for a while and it will all become second nature.
Edited by jwezorek
2

Share this post


Link to post
Share on other sites

I come to agree about using const wherever possible. Whenever I see a function accepting a non-const argument that's not simply a value I always want to know what it's going to change. This is really important in public interfaces, since even if the designer doesn't intend to change something passed as non-const there could be bugs in the code that change it anyway. It's worthwhile to pass values as const as well since const arguments clearly represent state external to the function rather than being just another variable in the function to be freely modified. I'm not saying that you can't pass by value and then modify the value if you really must, but if you're not doing so then const will help guard against bugs.

 

I'm curious about that video but I'm working with bandwidth limits here. Is there a print source that describes the change? Googling "C++11 const changes" didn't get me anywhere interesting.

0

Share this post


Link to post
Share on other sites

I'm curious about that video but I'm working with bandwidth limits here. Is there a print source that describes the change? Googling "C++11 const changes" didn't get me anywhere interesting.

 

Basically Sutter says that the standard library can only guarantee race condition free, and therefore well defined, use if all const parameters passed to it are never modified, or are internally synchronized. He therefore says that if you do need to modify the state of a const object, you should only do it though internally synchronized objects that are declared mutable. He goes on to say that mutable should be used by default on all internally synchronized objects, like atomics and mutexes.

 

Therefore "const" means "thread safe", and "mutable" means "internally synchronized".

 

This is a more stringent constraint on const than implied by the previous standard and Sutter says that any code using const_cast or mutable should be examined in light of the change.

Edited by King Mir
0

Share this post


Link to post
Share on other sites

Wow, thanks for all the replies everyone! 
I'm in the process of following the Rule of Three in my code; after some trial and error, I have a handle on the concept.  It's a small project, so I won't be using smart pointers yet - however I'll keep them in mind for future projects.

 

Once again, thanks for all the insight and time!

0

Share this post


Link to post
Share on other sites

By the way, to the OP, I think your on the right track wanting to learn about const correctness and the rule of three,.

 

Another good thing to learn at this point, if you don't already know, is how to use the std library smart pointer classes std::unique_ptr and std::shared_ptr.

1

Share this post


Link to post
Share on other sites

I think you would be on a better track learning the Rule of Two and RAII (repeated because it was likely lost in the slew of other previous replies).

Honestly, RAII probably takes priority over either Rule or Three or Rule of Two, which is not to say the rules are not important, but RAII is just such a core principal that can and should be applied vigorously throughout all of your code, as it prevents memory leaks (even complicated ones caused by exceptions), failure to release file handles, failure to leave critical sections, failure to delete pointers, etc.

 

Failure to follow one of the Rules of X will bite you in the ass when you copy complex objects, but from a performance standpoint that is something you should be trying to avoid as much as possible anyway.  While it is still sometimes necessary to copy complex objects, you will get bitten in the ass far more often by failing to release some resource or leave a critical section, as these cases simply happen magnitudes more often in practice than copying complex objects.

 

So once you have thoroughly employed RAII, Rule of Three no longer makes sense.

Better to prioritize RAII and then go with Rule of Two without wasting time on the less-safe (due to exceptions) and more time-consuming Rule of Three.

 

 

L. Spiro

2

Share this post


Link to post
Share on other sites

Wow, thanks for all the replies everyone! 
I'm
in the process of following the Rule of Three in my code; after some
trial and error, I have a handle on the concept.  It's a small project,
so I won't be using smart pointers yet - however I'll keep them in mind
for future projects.

 

Once again, thanks for all the insight and time!

 

As jwezorek says, you do want to learn and use smart pointers. Even in small projects. Smart pointers and STL containers are the preferred way of managing memory so that you don't have a leak.

 

The rule of three has it's place, but smart pointers make it much more rare.

 

I think you would be on a better track learning the Rule of Two and RAII (repeated because it was likely lost in the slew of other previous replies).

Honestly, RAII probably takes priority over either Rule or Three or Rule of Two, which is not to say the rules are not important, but RAII is just such a core principal that can and should be applied vigorously throughout all of your code, as it prevents memory leaks (even complicated ones caused by exceptions), failure to release file handles, failure to leave critical sections, failure to delete pointers, etc.

 

Failure to follow one of the Rules of X will bite you in the ass when you copy complex objects, but from a performance standpoint that is something you should be trying to avoid as much as possible anyway.  While it is still sometimes necessary to copy complex objects, you will get bitten in the ass far more often by failing to release some resource or leave a critical section, as these cases simply happen magnitudes more often in practice than copying complex objects.

 

So once you have thoroughly employed RAII, Rule of Three no longer makes sense.

Better to prioritize RAII and then go with Rule of Two without wasting time on the less-safe (due to exceptions) and more time-consuming Rule of Three.

 

 

L. Spiro

 

I agree that RAII is a concept that johnmarinelli needs to learn, but he could do without the rule of two. I made this objection earlier in the thread. You don't want a single object managing more than one resource. Also, writing a rudimentary smart pointer, then implementing different copy semantics on top of it, as link you showed suggests, is not, in my opinion, generally the best way to ensure RAII.

 

EDIT:These quote tag are very annoying to work with!

Edited by King Mir
0

Share this post


Link to post
Share on other sites

I agree that RAII is a concept that johnmarinelli needs to learn, but he could do without the rule of two. I made this objection earlier in the thread. You don't want a single object managing more than one resource. Also, writing a rudimentary smart pointer, then implementing different copy semantics on top of it, as link you showed suggests, is not, in my opinion, generally the best way to ensure RAII.

You probably don't want to manage resources in your objects at all, for that matter, but get references to them from an object pool/manager/cache of some sort that actually does the lifetime management of the resources.
But sometime it's even not about resources. Do you need to follow the rule of three if your object has a const POD member?

Also, i saw the linked article to be more about not needing the rule of three if you use smart pointers/RAII, and not about implementing your own smart pointers. Edited by Strewya
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0