阻止用户创建 class 的未命名实例
Preventing users from creating unnamed instances of a class
对于许多RAII "guard" classes,被实例化为匿名变量根本没有意义:
{
std::lock_guard<std::mutex>{some_mutex};
// Does not protect the scope!
// The unnamed instance is immediately destroyed.
}
{
scope_guard{[]{ cleanup(); }};
// `cleanup()` is executed immediately!
// The unnamed instance is immediately destroyed.
}
来自this article:
Anonymous variables in C++ have “expression scope”, meaning they are destroyed at the end of the expression in which they are created.
有什么方法可以防止用户在没有名字的情况下实例化它们吗? ("Prevent"可能太强了 - "making it very difficult" 也是可以接受的)。
我可以想到两种可能的解决方法,但它们在使用 class:
时引入了语法开销
在 detail
命名空间中隐藏 class 并提供一个宏。
namespace detail
{
class my_guard { /* ... */ };
};
#define SOME_LIB_MY_GUARD(...) \
detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}
这行得通,但是 hackish。
只允许用户通过高阶函数使用守卫。
template <typename TArgTuple, typename TF>
decltype(auto) with_guard(TArgTuple&& guardCtorArgs, TF&& f)
{
make_from_tuple<detail::my_guard>(std::forward<TArgTuple>(guardCtorArgs));
f();
}
用法:
with_guard(std::forward_as_tuple(some_mutex), [&]
{
// ...
});
当守卫 class 的初始化具有 "fluent" 语法时,此解决方法不起作用:
{
auto _ = guard_creator()
.some_setting(1)
.some_setting(2)
.create();
}
有没有更好的选择?我可以访问 C++17 功能。
您可以使用可扩展的 lint 工具,例如 Vera++ https://bitbucket.org/verateam/vera/wiki/Home 它可以让您对代码进行 lint,您可以使用 Python 或 tcl 创建新规则(我更喜欢 Python)
可能的流程是 - 在每次提交后,您的 CI 系统(例如 Jenkins)将 运行 执行 Vera++ 并验证此类疏忽的作业,如果失败,将发出邮件致提交者。
我想到的唯一明智的方法是让用户将 guard_creator::create
的结果传递给一些 guard_activator
,后者将左值引用作为参数。
这样,class 的用户别无选择,只能用名称创建对象(大多数开发人员会做的明智的选择),或者 new
然后取消引用(疯狂选项)
例如,您在评论中说您在非分配异步链创建器上工作。我可以考虑一个看起来像这样的 API:
auto token = monad_creator().then([]{...}).then([]{...}).then([]{...}).create();
launch_async_monad(token); //gets token as Token&, the user has no way BUT create this object with a name
防止 class 被实例化的规范方法是使它的构造函数成为 private
。要实际获得所需的实例之一,您可以调用 static
方法,该方法 returns 是对构造对象的引用。
class Me {
public:
static Me &MakeMe() { return Me(); }
private:
Me();
}; // Me
这当然没有帮助 - 但它可能会让程序员暂停!
int main() {
Me(); // Invalid
Me m; // Invalid
Me::MakeMe(); // Valid - but who'd write that?
Me m = Me::MakeMe();
} // main()
我知道这不是您描述的Guard
实例的直接模拟 - 但也许您可以调整这个概念?
如果可以使用 C++17 的全部潜力,您可以将使用静态工厂函数的想法扩展为有用的东西:保证复制省略使得静态工厂函数成为可能,即使对于不可移动的 类,如果 return 值被忽略,[[nodiscard]] 属性会提示编译器发出警告。
class [[nodiscard]] Guard {
public:
Guard(Guard& other) = delete;
~Guard() { /* do sth. with _ptr */ }
static Guard create(void* ptr) { return Guard(ptr); }
private:
Guard(void* ptr) : _ptr(ptr) {}
void* _ptr;
};
int main(int, char**) {
Guard::create(nullptr);
//auto g = Guard::create(nullptr);
}
对于许多RAII "guard" classes,被实例化为匿名变量根本没有意义:
{
std::lock_guard<std::mutex>{some_mutex};
// Does not protect the scope!
// The unnamed instance is immediately destroyed.
}
{
scope_guard{[]{ cleanup(); }};
// `cleanup()` is executed immediately!
// The unnamed instance is immediately destroyed.
}
来自this article:
Anonymous variables in C++ have “expression scope”, meaning they are destroyed at the end of the expression in which they are created.
有什么方法可以防止用户在没有名字的情况下实例化它们吗? ("Prevent"可能太强了 - "making it very difficult" 也是可以接受的)。
我可以想到两种可能的解决方法,但它们在使用 class:
时引入了语法开销在
detail
命名空间中隐藏 class 并提供一个宏。namespace detail { class my_guard { /* ... */ }; }; #define SOME_LIB_MY_GUARD(...) \ detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}
这行得通,但是 hackish。
只允许用户通过高阶函数使用守卫。
template <typename TArgTuple, typename TF> decltype(auto) with_guard(TArgTuple&& guardCtorArgs, TF&& f) { make_from_tuple<detail::my_guard>(std::forward<TArgTuple>(guardCtorArgs)); f(); }
用法:
with_guard(std::forward_as_tuple(some_mutex), [&] { // ... });
当守卫 class 的初始化具有 "fluent" 语法时,此解决方法不起作用:
{ auto _ = guard_creator() .some_setting(1) .some_setting(2) .create(); }
有没有更好的选择?我可以访问 C++17 功能。
您可以使用可扩展的 lint 工具,例如 Vera++ https://bitbucket.org/verateam/vera/wiki/Home 它可以让您对代码进行 lint,您可以使用 Python 或 tcl 创建新规则(我更喜欢 Python)
可能的流程是 - 在每次提交后,您的 CI 系统(例如 Jenkins)将 运行 执行 Vera++ 并验证此类疏忽的作业,如果失败,将发出邮件致提交者。
我想到的唯一明智的方法是让用户将 guard_creator::create
的结果传递给一些 guard_activator
,后者将左值引用作为参数。
这样,class 的用户别无选择,只能用名称创建对象(大多数开发人员会做的明智的选择),或者 new
然后取消引用(疯狂选项)
例如,您在评论中说您在非分配异步链创建器上工作。我可以考虑一个看起来像这样的 API:
auto token = monad_creator().then([]{...}).then([]{...}).then([]{...}).create();
launch_async_monad(token); //gets token as Token&, the user has no way BUT create this object with a name
防止 class 被实例化的规范方法是使它的构造函数成为 private
。要实际获得所需的实例之一,您可以调用 static
方法,该方法 returns 是对构造对象的引用。
class Me {
public:
static Me &MakeMe() { return Me(); }
private:
Me();
}; // Me
这当然没有帮助 - 但它可能会让程序员暂停!
int main() {
Me(); // Invalid
Me m; // Invalid
Me::MakeMe(); // Valid - but who'd write that?
Me m = Me::MakeMe();
} // main()
我知道这不是您描述的Guard
实例的直接模拟 - 但也许您可以调整这个概念?
如果可以使用 C++17 的全部潜力,您可以将使用静态工厂函数的想法扩展为有用的东西:保证复制省略使得静态工厂函数成为可能,即使对于不可移动的 类,如果 return 值被忽略,[[nodiscard]] 属性会提示编译器发出警告。
class [[nodiscard]] Guard {
public:
Guard(Guard& other) = delete;
~Guard() { /* do sth. with _ptr */ }
static Guard create(void* ptr) { return Guard(ptr); }
private:
Guard(void* ptr) : _ptr(ptr) {}
void* _ptr;
};
int main(int, char**) {
Guard::create(nullptr);
//auto g = Guard::create(nullptr);
}