RAII和工厂设计模式

RAII and factory design pattern?

本文关键字:设计模式 工厂 RAII      更新时间:2023-10-16

假设我有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,但我不确定是否保证使用移动构造函数/操作员=从 Foo::create返回?

复制elision不会帮助您,因为这是一种优化,并且可能不会应用或可能不会应用。

移动语义确实会有所帮助,并且您可以保证在局部变量的功能返回方面获得移动。但这意味着 you 必须编写移动构造函数,并且 you 必须修改击曲线,以免它解锁已移动的对象的资源。<<<<<</p>

不确定这与工厂模式有何关系,但是要回答您的问题"在这样的块中只有一次?":

避免使用用作返回值的复制/移动对象(即返回值优化,尤其是命名的返回值优化),但不能保证:

在以下情况下,允许编译器,但 不需要省略副本和移动 - (由于C 11)构造 of 类对象即使复制/移动(自C 11)构造函数和 破坏者具有可观察到的副作用。这是一个优化:甚至 当它发生并且未调用复制/移动构造者时, 仍然必须存在并且可以访问(好像没有发生优化 否则该程序的形式不佳:

如果函数返回按值返回类类型,然后返回 语句的表达是一个非易失性对象的名称 自动存储持续时间,这不是函数参数,或 捕获子句参数,并且具有相同类型(忽略 最高级CV合并)作为函数的返回类型,然后 省略复制/移动(由于C 11)。当该本地对象是 构造,它直接在存储中构造 否则将移动或复制函数的返回值。这 复制省略的变体称为nrvo,"命名返回值 优化"。

正如您在问题中提到的那样

另一种方法可能是使用shared_ptr,以便由RAII风格的shared_ptr包装器管理Foo -Object的创建和删除。如果您想保留 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();
}

规则为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 << "blockn"; }
    void unblock() { std::cout << "unblockn"; }
    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) {}
};

您正在寻找复制。

简而言之,您的代码保证在C 17中工作,如http://www.open-std.org/jtc1/sc22/wg21/wg21/docs/papers/2015/p0135r0.html0/P>

在C 14中,在没有这样的保证之前。

复制省略是一种优化,在C 11之前存在,并允许编译器在某些情况下省略复制构造函数。

c 11添加了移动语义和副本elision的扩展,以允许编译器避免移动或复制(如果没有移动)。

不管编译器实际上是什么,您的班级仍然必须提供副本或移动构造函数,即使编译器不使用一个。

c 17引入了"保证的复制率",它使您能够像在情况下返回不可移动类的对象。请注意,建议明确提到"工厂功能"。Quote:

编写工厂功能是不可能或非常困难的

提案的示例具有此示例:

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

我看到了解决此问题的两种方法:

  • 使用C 17:我建议删除副本和移动构造函数(和operator=)以确保您的代码不会在早期标准下编译具有错误效果的标准。
  • 仅依靠C 14中可用的内容来使您的代码在任何地方工作。您可能需要在对象中添加其他状态,删除复制构造函数并实现MOVE MOVE构造函数。

这是在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};
};