我们说我有Foo
课程:
struct Resource {
void block();
void unblock();
};
struct Foo {
static Foo create() {
Resource resource;
resource.block();
return Foo{resource};
}
~Foo() { resource.unblock(); }
void f() {}
private:
Resource resource;
Foo(Resource resource): resource(resource) {}
};
我是对的,并且无法保证~Foo
只会在此类块中被调用一次吗?
{
Foo foo = Foo::create();
foo.f();
}
如果没有保证,是否有可能以某种方式修复如果使用c ++ 11并移动语义?例如,不要在移动的foo中调用unblock_resource
,但我不确定是否保证使用移动构造函数/ operator =从Foo::create
返回?
答案 0 :(得分:4)
复制elision对您没有帮助,因为它是一种优化,可能会也可能不会应用。
移动语义确实有帮助,并且您可以在函数返回局部变量时得到保证。但这意味着你必须编写移动构造函数,你必须修改析构函数,以便它不会解锁被移动的对象的资源。 / p>
答案 1 :(得分:2)
不确定这与工厂模式有什么关系,但要回答你的问题“我是否正确,并且无法保证在这样的阻止中只会调用~Foo一次?”:
允许复制/移动用作返回值的对象(即返回值优化,特别是命名返回值优化),但不能保证:
在以下情况下,允许使用编译器,但是 不需要省略复制和移动 - (自C ++ 11)构造 类对象即使复制/移动(自C ++ 11)构造函数和 析构函数具有可观察到的副作用。这是一个优化:甚至 当它发生并且没有调用copy- / move-构造函数时,它 仍然必须存在且可访问(好像没有优化发生 在所有情况下),否则该程序是不正确的:
如果函数按值返回类类型,则返回 statement的表达式是一个非易失性对象的名称 自动存储持续时间,不是函数参数,或者是 catch子句参数,它具有相同的类型(忽略 顶级cv-qualification)作为函数的返回类型,然后 复制/移动(因为C ++ 11)被省略。当那个本地对象是 建造后,它直接在仓库中建造 否则将移动或复制函数的返回值。这个 复制elision的变体被称为NRVO,“命名返回值 优化”。
一种方法是控制移动构造函数和析构函数中的锁定/解锁资源,就像您在问题中提到的那样。
另一种方法是使用shared_ptr
,以便创建和删除Foo
- 对象由RAII样式的shared_ptr包装器管理。如果你想让Foo
- 构造函数保持私有,那么只有一件棘手的事情,因为make_shared
无法处理私有构造函数。为了解决这个问题,您可以声明一个公共构造函数,将私有数据类型的参数作为参数。由于shared_ptr
- 包装器,有点难看,可能有点笨拙。但也许这至少是一些灵感:
struct Foo {
private:
struct private_dummy {};
public:
static shared_ptr<Foo> create() {
shared_ptr<Foo> foo = make_shared<Foo>(private_dummy{});
return foo;
}
~Foo() { cout << "deleted."; }
Foo(struct private_dummy x) { cout << "created."; }
};
void test() {
shared_ptr<Foo> foo = Foo::create();
}
int main() {
test();
//Foo notOK();
}
答案 2 :(得分:1)
您正在寻找copy-elision。
简而言之,您的代码可以保证在C ++ 17中工作,如http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html
所述在C ++ 14中,之前没有这样的保证。
复制elision是一种在C ++ 11之前存在的优化,并允许编译器在某些情况下省略复制构造函数调用。
C ++ 11添加了移动语义,并且扩展了复制省略以允许编译器避免移动或复制(如果移动不可用)对象。
无论编译器实际执行什么操作,您的类仍必须提供复制或移动构造函数,即使编译器不会使用它。
C ++ 17引入了“保证copy-elision”,它允许您返回不可移动类的对象,就像您的情况一样。请注意,提案明确提到“工厂功能”。引用:
编写工厂函数是不可能或非常困难的
提案中的示例有此示例:
struct NonMoveable {
NonMoveable(int);
NonMoveable(NonMoveable&) = delete;
void NonMoveable(NonMoveable&) = delete;
std::array<int, 1024> arr;
};
NonMoveable make() {
return NonMoveable(42); // ok, directly constructs returned object
}
截至今天,Clang和GCC都能够使用-std=c++17
标志编译该代码,但不能使用-std=c++14
编译。
我认为有两种方法可以解决这个问题:
operator=
)以确保您的代码不会在早期标准下编译并带有错误效果。这是如何在C ++ 14中完成的一个例子。
class Foo {
public:
Foo() = default;
Foo(const Foo &) = delete;
Foo(Foo &&rvalue) noexcept { std::swap(blocked, rvalue.blocked); }
~Foo() { if (blocked) unblock();
void block() { blocked = true; }
void unblock() { blocked = false; }
private:
bool blocked{false};
};
答案 3 :(得分:1)
3/5/0的规则。您可以定义析构函数,但不能复制/移动构造函数/赋值运算符,这是一个类型不安全的红色标记。实际上,析构函数可能在C ++ 17之前被调用两次,并且即使在C ++ 17之后使用它也很容易搞乱。
我建议使用std::unique_ptr
,这样就不会定义任何复制/移动操作或析构函数。即使您管理的资源不是指针,也可以使用std::unique_ptr
;它看起来像这样:
class Resource {
int handle;
public:
Resource(std::nullptr_t = nullptr)
: handle{}
{}
Resource(int handle)
: handle{ handle }
{}
explicit operator bool() const { return handle != 0; }
friend bool operator==(Resource lhs, Resource rhs) { return lhs.handle == rhs.handle; }
friend bool operator!=(Resource lhs, Resource rhs) { return !(lhs == rhs); }
void block() { std::cout << "block\n"; }
void unblock() { std::cout << "unblock\n"; }
struct Deleter {
using pointer = Resource;
void operator()(Resource resource) const {
resource.unblock();
}
};
};
struct Foo {
static Foo create() {
Resource resource{42};
resource.block();
return Foo{resource};
}
void f() {}
private:
std::unique_ptr<Resource, Resource::Deleter> resource;
Foo(Resource resource): resource(resource) {}
};