防止用户创建类的未命名实例
Preventing users from creating unnamed instances of a class
对于许多RAII"guard">类,被实例化为匿名变量根本没有意义:
{
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.
}
从这篇文章:
C++中的匿名变量具有"表达式作用域",这意味着它们在创建它们的表达式末尾被销毁。
有没有办法防止用户在没有名称的情况下实例化它们?("预防"可能太强了 - "使其非常困难"也是可以接受的)。
我可以想到两种可能的解决方法,但它们会在使用类时引入语法开销:
将类隐藏在
detail
命名空间中并提供宏。namespace detail { class my_guard { /* ... */ }; }; #define SOME_LIB_MY_GUARD(...) detail::my_guard MY_GUARD_UNIQUE_NAME(__LINE__) {__VA_ARGS__}
这有效,但很黑客。
仅允许用户通过高阶函数使用防护。
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), [&] { // ... });
当保护类的初始化具有"流畅"语法时,此解决方法不起作用:
{ auto _ = guard_creator() .some_setting(1) .some_setting(2) .create(); }
有没有更好的选择?我可以使用 C++17 功能。
我想到的唯一明智的方法是让用户将guard_creator::create
的结果传递给某些将左值引用作为参数的guard_activator
。
这样,类的用户就没有办法,只能使用名称创建对象(大多数开发人员会做的理智选项),或者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
如果能够充分利用 C++17 的潜力,则可以将使用静态工厂函数的想法扩展为有用的东西:保证复制 elision 使静态工厂函数即使对于不可移动的类也可以使用,并且 [[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);
}
在编译器资源管理器中编译
你可以使用可扩展的lint工具,如Vera++ https://bitbucket.org/verateam/vera/wiki/Home 它允许你lint你的代码,你可以使用Python或tcl创建新规则(我更喜欢Python)
一个可能的流程是 - 在每次提交后,您的 CI 系统(例如 Jenkins)将运行执行 Vera++ 的作业并验证此类监督,如果失败,将向提交者发送邮件。
防止类被实例化的规范方法是使其构造函数private
。若要实际获取所需的实例之一,请调用static
方法,该方法返回对构造对象的引用。
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
实例的直接类比 - 但也许您可以调整这个概念?
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 为什么未命名的结构内联变量在每个翻译单元中没有相同的地址?
- C++中未命名函数指针的语法
- 未命名的非类型模板参数有什么意义?
- C++包含函数标头会给出错误'__dest'未命名类型
- 不同C++文件中未命名命名空间中的名称可以引用同一个命名事物吗?
- 用于创建未命名实例的语法
- 错误:"cast"未命名类型void setCastDescription(std::string
- 我可以定义一个 constexpr 匿名/未命名变量吗?
- C++ 错误:(类名)未命名类型
- 如何访问嵌套在命名命名空间中的未命名命名空间变量?
- 未命名的函数参数用法
- 使用 std ::transform 构造 std::vector.返回未命名结果的可能性?
- 为什么将字符串输出到未命名的 std::ofstream 反而给了我一个十六进制数?
- 在多个 cpp 文件中重用未命名的命名空间函数
- C++ ,错误:命名空间'std'中的'shared_ptr'未命名模板类型
- src/caffe/parallel.cpp:70:1:错误:“参数”未命名类型
- 错误:'int_type'未命名类型 - 如何继承 typedefs 和用法
- 如何修复错误,迭代器未在此范围内声明,并且迭代器未命名类型'
- 防止用户创建类的未命名实例