Ideally, a very limited number of (re)initialization paths should exist within a type. Two of them are obvious: the constructor and the 'operator ='. Both are responsible for establishing object's invariants. The third one, not so obvious, is just a plane swap() function that can help us to get rid of all the setters() and resetters() by reusing the well established path of the constructor as demonstrates the example blow:
VIEW THE CODE BELOW IN FULL-SCREEN (swap_sample.cpp)
Code: Select all
/*
(c) 2012 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=725
*/
#include <string>
#include <algorithm>
#include <cassert>
#include <iostream>
class Person
{
public:
Person(const std::string &first_name, const std::string &last_name, int age):
_first_name(first_name), _last_name(last_name), _age(age)
{
// in general here might come quite a complex initialization, it would
// be error prone to duplicate the logic in setters() and resetters()
assert(!_first_name.empty() && !_last_name.empty());
_initials = std::string(1, _first_name[0]) + std::string(1, _last_name[0]);
assert(!std::any_of(_initials.begin(), _initials.end(), ::islower));
_full_info = _first_name + " " + _last_name + ", aka " + _initials;
}
// just keep swap() in sync with the constructors and 'operator =', that's all!
void swap(Person &other)
{
std::swap(_first_name, other._first_name);
std::swap(_last_name, other._last_name);
std::swap(_age, other._age);
std::swap(_initials, other._initials);
std::swap(_full_info, other._full_info);
}
// just a bunch of getters, there's no need for setters() nor resetters() !!!
const std::string & first_name() const { return _first_name; }
const std::string & last_name() const { return _last_name; }
int age() const { return _age; }
// more getters...
const std::string & full_info() { return _full_info; }
private:
std::string _first_name;
std::string _last_name;
int _age;
// in general here come various derived or cached data...
std::string _initials;
std::string _full_info;
};
int main()
{
Person p("Jonh", "Doe", 25);
std::cout << p.full_info() << " (at " << &p << ")\n";
// here comes the magic, set the last name by swapping the whole object
Person(p.first_name(), "Foo", p.age()).swap(p);
std::cout << p.full_info() << " (at " << &p << ")\n";
// a full reset is also trivial, note that all the swaps() preserve
// object's identity (address)
Person("Jane", "Roe", 18).swap(p);
std::cout << p.full_info() << " (at " << &p << ")\n";
return 0;
}
// output:
// Jonh Doe, aka JD (at 002BF820)
// Jonh Foo, aka JF (at 002BF820)
// Jane Roe, aka JR (at 002BF820)
It's also worth mentioning that the overhead of swap() is usually pretty insignificant, especially with the move semantics that C++11 brings.