const&
to pass-by-value
const&
when should return-by-value
private
your Data Members
std::swap
overload
Seems easy, until you try it.
Consider the class Date
:
class Date{
...
public:
Date(int month, int day, int year);
};
This requires the user to look up the docs, which not 100% do. Furthermore, what if the user constructs something weird like Date date(-1,40,300);
?
It makes life easier for the user if he was given a clear type error, like:
class Date{
...
public:
Date(Month m, Day d, Year y);
};
This requires the user to be fully aware that he’s inputting a Year
where a year should be.
Furthermore, we can do something like this for Month
:
class Month{
public:
static Month Jan(){ return Month(1); }
...
private:
Month(int month){ ... }
};
The static
keyword allows us to return the same statically allocated object. Locally static variables are created the moment the function is called, remember?
Another thing we can do is use const
, which should be pretty obvious by now. It prevents things like:
const val& operator*(const val& lhs, const val& rhs) {
...
}
// this is not allowed! (and rightfully so)
(a*b = c)
It’s ideal if the class design behaves the same way as a built-in type.
This is just a list of questions to ask myself:
operator=
is not a ctor.private
?
const&
to pass-by-valueIt’s extremely inefficient to copy by-value a large object, as it is likely to have many stl containers that also need to be recursively copied.
For example:
class Banker{
...
std::vector<Client> clients;
};
Banker banker;
pass_value(banker);
This copies over banker, but also recursively copies over clients! What if the banker is good at his job? Then this function will be very slow.
This occurs during polymorphic functions:
class Base{
...
}
class Derived : Base{
...
}
void foo(Base b){
...
}
Derived derived;
foo(derived); // Slices the Derived portion off!
This will pass-by-copy, meaning it will call the constructor for Base
, rather than Derived
in this situation. The issue here is that only the Base
class will be created, thus any overloaded functions(thus useful applications) of inherited classes are rendered moot.
The way to fix this is using a const Base& b
:
class Base{
...
}
class Derived : Base{
...
}
void foo(const Base& b){
...
}
Derived derived;
foo(derived); // Totally OK!
Why does this work? Because references are implemented as pointers underneath. You always pass by pointer when you want to support polymorphic behavior. OK?
const&
when should return-by-valueSometimes, const&
just doesn’t work. Consider the following operator*
overload:
const Foo& operator*(const Foo& lhs, const Foo& rhs){
Foo foo;
return *foo;
}
Doesn’t work. You just allocated something and returned the reference when the object just free’d itself. You are now looking at undefined behavior.
const Foo& operator*(const Foo& lhs, const Foo& rhs){
Foo *foo = new Foo();
return *foo;
}
Works… If you don’t consider chaining statements. Who’s going to delete this either? Who has the chance to either in this situation:
// captures the final result...
// But what happens to the `new` f1*f2?
Foo& foo = ((f1*f2)*f3);
Doesn’t work. Memory leak!
const Foo& operator*(const Foo& lhs, const Foo& rhs){
static Foo foo;
foo = ...;
return *foo;
}
This will return something that will be deleted at the very end of the program, and so there’s no real memory leak. However, this could occur:
if((a*b) == (c*d)) // ALWAYS TRUE!
... // do something
Why always true
? Because both (a*b)
and (c*d)
return the SAME static reference! Of course it would be equal - they’re the same pointer.
private
your Data MembersThe user shouldn’t remember whether to get a result via foo.val
or foo.val()
. Because sometimes functions to retrieve processed values is inevitable, we can only move from members to functions.
You can choose read/write access, or both with functions, like so:
class Foo{
void setX(int _x){ x = _x; } // write access
int getX(){ return x; } // read access
private:
int x;
};
Can’t do that with public data members.
You can imagine that we shouldn’t store all the values if we can derive it somehow. This is important for low memory devices(embedded).
class Foo{
int getY(){ return x*2; } // we don't have a y member!
private:
int x;
};
Plus, we can make sure that interfaces are not broken by the change of internals. For this same reason, we do not want to use protected
ever.
Pretty much, if you can move a member function out as a non-member non-friend function, you are moving a convenience function out of the core functionalities. This is good, because there should be as little interface to the data from the core functionalities as possible.
Further compositions can be just a list of convenience functions. Convenience functions can be moved to their own .cpp
and .h
files.
// file1.h
class Foo{
public:
...
int getA(){...}
int getB(){...}
int getAplusB(){...} // a convenience function.
private:
int a,b;
};
This will lead to monolithic .cpp
files. In retrospect, you could’ve done:
// file1.h
class Foo{
public:
...
int getA(){...}
int getB(){...}
private:
int a,b;
};
// file2.h
int getAplusB(const Foo& foo){...} // a convenience function.
When you overload the member function:
class Foo{
public:
...
const Foo operator+(const Foo& rhs){
...
};
private:
int a,b;
};
This just doesn’t work with implicit type conversions. This requires the lhs expression to be a foo as well.
If you wanted it to be a numeric type, then this is terrible. consider: 3 + foo
. This is 3.operator+(foo)
. That’s not what we wanted :(
So the natural fix is to move it as a non-member non-friend function. Non-friend because a friend function will decrease the encapsulation.
std::swap
overloadSometimes, std::swap
is slow and unnecessary. Under the hood, it looks like the most simple impl:
namespace std{
template<typename T>
void swap(T& lhs, T& rhs){
T temp(lhs); // SLOW!
lhs = rhs;
rhs = temp;
};
}
Now, we can introduce an idea of a pimpl. This idiom allows us to add a level of indirection by pointing our resources in a heap allocated ptr.
This also comes with the added benefit of not having to initialize a new object:
class Foo{
public:
...
private:
class Impl;
// everything in here!
std::unique_ptr<Impl> pimpl;
};
When we swap:
class Foo{
public:
...
private:
class Impl;
// everything in here!
std::unique_ptr<Impl> pimpl;
// This MUST be here, since pimpl is private.
// We are also specializing.
friend std::swap<>(Foo& lhs, Foo& rhs);
};
...
namespace std{
template <> // template specialization syntax
void swap<Foo>(Foo& lhs, Foo& rhs){
// FAST! Swapping a pointer is almost free
std::swap(lhs.pimpl, rhs.pimpl);
};
}
As we just saw above, the swap function needed to be friend
‘d. That’s not good, because it decreases the encapsulation of the class Foo
. So how do we alleviate this?
We can define our own member swap()
function inside of Foo, and direct the specialized std::swap
function to use that interface!
class Foo{
public:
...
private:
class Impl;
// everything in here!
std::unique_ptr<Impl> pimpl;
void swap(Foo& rhs){
using std::swap;
swap(pimpl, rhs.pimpl);
}
};
...
namespace std{
template <> // template specialization syntax
void swap<Foo>(Foo& lhs, Foo& rhs){
// FAST! Swapping a pointer is almost free
lhs.swap(rhs);
};
}
Suppose Foo
’s declaration is now:
template <typename T>
class Foo{
...
};
Then how do you exactly specialize this? If we just replace swap<Foo>
with swap<Foo<T>>
, it won’t compile. This is a partial template specialization, which is not allowed for functions.
Why are partial template specializations not allowed? Because you can achieve the same thing by function overloading:
// WILL NOT WORK
namespace std{
// This is a specialization
template <typename T>
void swap<Foo<T>>(Foo& lhs, Foo& rhs){
lhs.swap(rhs);
};
}
// WILL WORK (BUT ACTUALLY DOESNT)
namespace std{
// This is an overload
template <typename T>
void swap(Foo<T>& lhs, Foo<T>& rhs){
lhs.swap(rhs);
};
}
MMM… But it doesn’t actually work… You see, std namespace does NOT allow for new functions/classes/definitions. Specializations are OK, but new functions to overload are not. So what can we do?
// WILL WORK(BUT UGLY)
namespace foo_namespace{
class Foo { ... };
// This is an overload
template <typename T>
void swap(Foo<T>& lhs, Foo<T>& rhs){
lhs.swap(rhs);
};
}
Is the best thing we can do. It’s ugly, but refer to next section on how to use it effectively.
The specialization & overloading kind of forces you to keep track of the 3 functions you could face:
std::swap
defaultstd::swap
foo_namespace::swap
You don’t know which one’s going to be used, so what are you gonna do?
{
// calling swap() w/o overload will give us this.
using std::swap;
...
// all 3 functions are taken into account.
swap(foo1, foo2);
...
}
Is the cleanest way.