[英]RAII and factory design pattern?
假設我有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
返回?
復制elision對您沒有幫助,因為它是一種優化,可能會也可能不會應用。
移動語義確實有幫助,並且您可以在函數返回局部變量時得到保證。 但是,這意味着你必須編寫移動構造函數, 你必須修改析構函數,以便它不解鎖是從移動對象的資源。
不確定這與工廠模式有什么關系,但要回答你的問題“我是否正確,並且不能保證在這樣的塊中只能調用一次~Foo?”:
允許復制/移動用作返回值的對象(即返回值優化,尤其是返回值優化),但不能保證:
在下列情況下, 允許編譯器,但不要求省略復制和移動(自C ++ 11)類對象的構造 ,即使復制/移動(自C ++ 11)構造函數和析構函數具有可觀察到的副作用。 這是一個優化:即使它發生並且沒有調用copy- / move-構造函數,它仍然必須存在並且可訪問(好像根本沒有發生優化),否則程序是不正確的:
如果函數按值返回類類型,並且return語句的表達式是具有自動存儲持續時間的非易失性對象的名稱,該對象不是函數參數或catch子句參數,並且具有相同的類型(忽略頂級cv-qualification)作為函數的返回類型,然后省略復制/移動(因為C ++ 11)。 構造該本地對象時,它直接在存儲器中構造,否則將移動或復制函數的返回值。 復制省略的這種變體被稱為NRVO,“命名返回值優化”。
一種方法是控制移動構造函數和析構函數中的鎖定/解鎖資源,就像您在問題中提到的那樣。
另一種方法是使用shared_ptr
,這樣你的Foo
-object的創建和刪除由RAII風格的shared_ptr包裝器管理。 如果你想讓Foo
-constructor保持私有,那么只有一個棘手的事情,因為make_shared
不能處理私有構造函數。 為了解決這個問題,您可以聲明一個公共構造函數,將私有數據類型的參數作為參數。 由於shared_ptr
-wrapper,有點難看,也許有點笨拙。 但也許這至少是一些靈感:
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();
}
您正在尋找copy-elision 。
簡而言之,您的代碼保證可以在C ++ 17中使用,如http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0135r0.html所述。
在C ++ 14和之前沒有這樣的保證。
復制省略是在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/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) {}
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.