Comment smells
class MyWidget {
std::mutex mutex_;
public:
// don't forget to unlock
void lock();
// only valid if you locked
void unlock();
// must hold lock
void tinker_with(int amount);
};
void tinker(MyWidget &widget) {
widget.lock();
widget.tinker_with(123);
widget.tinker_with(333);
widget.unlock();
}
class MyWidget {
// ...
std::scoped_lock<std::mutex> lock();
// must hold lock
void tinker_with(int amount);
};
void tinker(MyWidget &widget) {
widget.lock();
widget.tinker_with(123);
widget.tinker_with(333);
}
class MyWidget {
// ...
[[nodiscard]]
std::scoped_lock<std::mutex> lock();
// must hold lock
void tinker_with(int amount);
};
error: ignoring return value of
'scoped_lock<mutex> MyWidget::lock()',
declared with attribute 'nodiscard'
| widget.lock();
| ^
void tinker(MyWidget &widget) {
auto lock = widget.lock();
widget.tinker_with(123);
widget.tinker_with(333);
}
// must hold lock
void tinker_with(int amount);
class MyWidget {
void tinker_with(int amount);
public:
class Tinkerable;
Tinkerable get_tinkerable() {
return Tinkerable(*this);
}
};
class MyWidget::Tinkerable {
MyWidget &widget_;
std::scoped_lock<std::mutex> lock_;
friend MyWidget;
explicit Tinkerable(MyWidget &widget)
: widget_(widget),
lock_(widget_.mutex_) {}
public:
void tinker_with(int amount) {
widget_.tinker_with(amount);
}
};
Using the Mutator interface
void tinker(MyWidget &widget) {
auto tinkerable = widget.get_tinkerable();
tinkerable.tinker_with(123);
tinkerable.tinker_with(333);
}
Don't call us, we'll call you
class MyWidget {
std::mutex mutex_;
class WidgetState {
int state = 123;
public:
void tinker_with(int amount);
};
WidgetState state_;
public:
template <typename TinkerFunc>
void tinker(TinkerFunc tinker_func);
};
Don't call us, we'll call you
template <typename TinkerFunc>
void MyWidget::tinker(
TinkerFunc tinker_func) {
std::scoped_lock lock(mutex_);
tinker_func(state_);
}
Don't call us, we'll call you
void tinker(MyWidget &widget) {
widget.tinker(
[](auto &tinkerable) {
tinkerable.tinker_with(123);
tinkerable.tinker_with(333);
}
);
}
Other comment smells
class ShaderRegistry {
public:
void add(const char *shader);
// once all shaders are added, compile
void compile();
// get a compiled shader by name.
// must be compiled and linked!
CompiledShader &get_compiled(
const char *name) const;
};
class CompiledShaders {
public:
CompiledShader &get_compiled(
const char *name) const;
};
class ShaderCompiler {
public:
void add(const char *shader);
[[nodiscard]]
CompiledShaders compile() const;
};
void use() {
ShaderCompiler compiler;
compiler.add("bob");
compiler.add("dawn");
auto shaders = compiler.compile();
shaders.get_compiled("bob").render();
}
class ShaderCompiler {
public:
void add(const char *shader);
// Resources used in compilation are
// transferred to the CompiledShaders:
// you cannot call compile() twice!!
[[nodiscard]]
CompiledShaders compile();
};
class ShaderCompiler {
public:
void add(const char *shader);
[[nodiscard]]
CompiledShaders compile() &&;
};
auto shaders = compiler.compile();
error: passing 'ShaderCompiler' as 'this' argument
discards qualifiers [-fpermissive]
| auto foo = sc.compile();
| ^
in call to 'CompiledShaders ShaderCompiler::compile() &&'
| CompiledShaders compile() &&;
| ^~~~~~~
auto shaders =
std::move(compiler)
.compile();
CompiledShaders compile() {
ShaderCompiler compiler;
compiler.add("bob");
compiler.add("dawn");
return compiler.compile(); // sadly this does not work :'(
}
void use() {
auto shaders = compile();
shaders.get_compiled("bob").render();
}
auto shaders =
std::move(compiler)
.compile();
shaders.get_compiled("bob").render();
// oops
compiler.add("persephone");
Compiler returned: 0
warning: 'compiler' used after it was moved
compiler.add("persephone");
^
move occurred here:
std::move(compiler)
^
Protect invariants with code
Apologetic comment anti-pattern
Separation of concerns
clang-tidy
is your friend