For compilers to parse and optimize,
for the processor to execute,
or for programmers to read and understand?
What is the depth of human mental stack, before overflow?
How many registers human have?
draw_rectangle(42, -11, 13, 17, 209, 162, 30, true, true);
void draw_rectangle(int x, int y, int width, int height,
uint8_t colorR, uint8_t colorG, uint8_t colorB,
bool isVisible = true, bool drawBorder = false);
Can this rule of thumb be counterproductive?
Repetition reduction increases complexity
and makes code harder to modify.
void cat()
{
std::clog << "start cat\n";
look_at_the_door();
eat("mouse");
sleep();
}
void dog()
{
std::clog << "start dog\n";
bark();
eat("your dinner");
sleep();
}
void cat()
{
std::clog << "start cat\n";
look_at_the_door();
eat("mouse");
sleep();
}
void dog()
{
std::clog << "start dog\n";
bark();
eat("your dinner");
sleep();
}
void pet(const std::string& species, const std::string& food,
const std::function<void(void)>& action)
{
std::clog << "start " << species << '\n';
action();
eat(food);
sleep();
}
void pet(const std::string& species, const std::string& food,
const std::function<void(void)>& action)
{
std::clog << "start " << species << '\n';
action();
eat(food);
sleep();
}
void cat()
{
pet("cat", "mouse", [](){ look_at_the_door(); } );
}
void dog()
{
pet("dog", "your dinner", [](){ bark(); } );
}
void pet(const std::string& species, const std::string& food,
const std::function<void(void)>& action)
{
std::clog << "start " << species << '\n';
action();
eat(food);
sleep();
}
void puppy()
{
std::clog << "start puppy\n";
eat("your shoe");
eat("your dinner");
tear_apart_your_home();
sleep();
}
void pet(const std::string& species, const std::string& food,
const std::function<void(void)>& action)
{
std::clog << "start " << species << '\n';
action();
eat(food);
sleep();
}
void cat()
{
pet("cat", "mouse", [](){ look_at_the_door(); } );
}
void dog()
{
pet("dog", "your dinner", [](){ bark(); } );
}
void pet(const std::string& species, const std::function<void(void)>& action)
{
std::clog << "start " << species << '\n';
action();
sleep();
}
void cat()
{
pet("cat",
[]()
{
eat("mouse");
look_at_the_door();
}
);
}
void puppy()
{
pet("puppy",
[]()
{
eat("your shoe");
eat("your dinner");
tear_apart_your_home();
}
);
}
If your code fits the abstraction, you don't have to make updates in multiple places.
... at the cost of increased complexity.
Where do you do your code reviews?
auto.cpp:18:9: error: cannot bind non-const lvalue reference
of type 'uint8_t&' {aka 'unsigned char&'}
to an rvalue of type 'uint8_t' {aka 'unsigned char'}
18 | bar(a);
| ^
// generated by https://cppinsights.io/
#include <cstdint>
extern void foo(const uint8_t &);
extern void bar(uint8_t &);
int main()
{
class __lambda_8_22 { /* ... */ };
const __lambda_8_22 add = __lambda_8_22{};
const uint8_t x = 13;
const uint8_t y = 42;
int a = add.operator()(x, y);
foo(static_cast<const unsigned char>(a));
// !?? bar(a);
}
class LegacyClass
{
uint8_t* owningPtr;
public:
LegacyClass(size_t size)
{
owningPtr = new uint8_t[size];
}
~LegacyClass()
{
delete owningPtr;
}
void legacy_method()
{
// may throw an exception, make sure memory is deallocated
std::unique_ptr<uint8_t> uptr(owningPtr);
owningPtr = nullptr;
// ...
throw std::runtime_error("");
}
};
Don't practice what you do not want to become.
Jordan Peterson
It is hard to change company culture. Be carefull what culture you are creating.
Unless you have a dedicated department for it...
it can be outdated, non existent or written in the code
Documentation should contain intentions and reasoning behind the code.
// The only practically maintainable form of documentation
// is documentation written in the code.
int main()
{
// What prevents you from writing few sentences, even a paragraph,
// directly at the place where it matters the most?
// BWT, documentation should help the reviewer to understand your code
// before merging.
return 0;
}
... is part of the documentation.
// ISSUE-123 Fixing missing example of practical issure tracking
// in my presentation
int main()
{
return 0;
}
# git commit -m "Commit demonstrating issue tracking (ISSUE-123)"
... are part of the documentation.
Name your constants. Even if you use them once.
void foo(std::vector<int> vec)
{
if(vec.size() > 13 || vec.size() < 7)
{
// ...
}
}
void foo(std::vector<int> vec)
{
const auto upperSizeThreshold = 13; // reasoning why it is 13
const auto lowerSizeThreshold = 7; // reasoning why it is 7
if(vec.size() > lowerSizeThreshold || vec.size() < upperSizeThreshold)
{
// ...
}
}
enum bitmask1
{
flag_a = 1,
flag_b = 2,
flag_c = 4,
};
enum bitmask2
{
flag_a = 1 << 0, // what flag a means
flag_b = 1 << 1, // what flag b means
flag_c = 1 << 2, // what flag c means
};
Beware of traps in string literals.
const char binaryLiteral0[] = "\x12\x34";
const char binaryLiteral1[] = "\x00\x01ALOHA";
sizeof(binaryLiteral1) == /* ? */; // 7
const char binaryLiteral2[] = "\x00\x00000000000000001ALOHA";
sizeof(binaryLiteral2) == 7;
const char binaryLiteral3[] = "\x00\x01" "ALOHA";
sizeof(binaryLiteral3) == 8;
... are part of the documentation.
void process_answer(const int number)
{
assert(number == 42);
// ...
}
void process_answer(const int number)
{
assert(number == 42 && "Universal answer must be 42");
// ...
}
Use compile time checks as much as you can for hidden expectations.
//// in some include
// using typeA_t = ???;
// using typeB_t = ???;
void foo(typeA_t a, typeB_t b);
void foo(const typeA_t a, const typeB_t b)
{
static_assert(std::is_same_v<typeA_t, typeB_t>,
"The types does not match, check the code below.");
static_assert(std::is_integral_v<typeA_t>,
"The typeA_t is not integral, check the code below.");
// ...
}
return codes
bool getData(const std::string& url, std::vector<uint8_t>& outData);
/* DWORD getData(const std::string& url, std::vector<uint8_t>& outData); */
std::vector<uint8_t> data;
const bool success = getData("meetingcpp.com", data);
if(success)
{
//...
}
exceptions
std::vector<uint8_t> getData(const std::string& url);
std::vector<uint8_t> data;
try
{
data = getData("meetingcpp.com");
}
catch(const std::runtime_error& ex)
{
// ...
}
std::optional
std::optional<std::vector<uint8_t>> getData(const std::string& url);
const std::optional<std::vector<uint8_t>> data = getData("meetingcpp.com");
if(data)
{
// ...
}
structured bindings
std::tuple<bool, std::vector<uint8_t>> getData(const std::string& url);
auto [success, data] = getData("meetingcpp.com");
if(success)
{
// ...
}
Inspired by sudo (Linux) and UAC (Windows)
class BasicInterface
{
public:
virtual ~BasicInterface() {}
virtual int getValue() const = 0;
virtual void setValue(int) = 0;
};
class ElevatedInterface : public BasicInterface
{
public:
virtual ~ElevatedInterface() {}
virtual std::lock_guard<std::mutex> sync() = 0;
virtual void addValueListener(std::function<void(int)>) = 0;
};
class Implementation : public ElevatedInterface
{
// ...
};
extern void engineFunction(ElevatedInterface&);
extern void userFunction(BasicInterface&);
int main()
{
Implementation instance;
engineFunction(instance);
userFunction(instance);
return 0;
}
void userFunction(BasicInterface& iface)
{
// If user code needs for some reason elevated interface,
// it has to be casted.
ElevatedInterface& elevatedIface = dynamic_cast<ElevatedInterface&>(iface);
}
... like in Catch unit-testing framework
SCENARIO( "vectors can be sized and resized", "[vector]" )
{
GIVEN( "A vector with some items" )
{
std::vector v( 5 );
REQUIRE( v.size() == 5 );
}
}
class Interface
{
public:
virtual ~Interface() {}
virtual void foo() const = 0;
};
using factoryFunc_t = std::unique_ptr<Interface>(void);
std::vector<std::function<factoryFunc_t>>& getAutoregistrationContained()
{
static std::vector<std::function<std::unique_ptr<Interface>(void)>> container;
return container;
}
template<typename DERIVED>
class Parrent : public Interface
{
public:
virtual ~Parrent() {}
protected:
static std::unique_ptr<Interface> createInstance( )
{
return std::unique_ptr<Interface>( new DERIVED() );
}
class RegistrationHelper
{
public:
RegistrationHelper() : registered(true)
{
auto& container = getAutoregistrationContained();
container.push_back(createInstance);
}
};
static RegistrationHelper s_registrationHelper;
};
class Child : public Parrent<Child>
{
public:
virtual ~Child() {}
void foo() const override
{
std::cout << "foo" << '\n';
}
};
template<typename DERIVED>
typename Parrent<DERIVED>::RegistrationHelper Parrent<DERIVED>::s_registrationHelper;
template class Parrent<Child>;
int main()
{
std::cout << "size " << getAutoregistrationContained().size()
<< " autoregistered classes\n";
for(std::function<factoryFunc_t>& factory : getAutoregistrationContained())
{
std::unique_ptr<Interface> instance = factory();
instance->foo();
}
return 0;
}
Is it worth it?
by forcing you to expose the container type with its iterators.
class Foo;
class Implementation : public Interface
{
public:
// ...
private:
std::vector<Foo> container;
// std::list<Foo> container;
};
class Interface
{
public:
virtual std::vector<Foo>::iterator begin() = 0;
virtual std::vector<Foo>::iterator end() = 0;
};
class Interface
{
public:
using callback_t = void(Foo&);
virtual void for_each(const std::function<callback_t>&) = 0;
};
Implementation instance;
Interface2& interfacedInstance = instance;
instance.for_each
(
[](Foo& foo)
{
// do the single iteration
}
);
Or create your own iterator hiding the real one...
what leads you into a deep, deep rabbit hole.
Thanks to Hana Dusíková, Pavel Šrámek