C++:是否必须将策略放入包装器中以避免裸露的新建/删除?

发布于 2024-12-29 08:19:56 字数 1258 浏览 1 评论 0原文

所以我发现了策略的乐趣。

class Strategy
{
public:
    virtual void action() = 0;
};

class FooStrategy : public Strategy
{
    virtual void action() { /* do some stuff */ };
};

class BarStrategy : public Strategy
{
    virtual void action() { /* do different stuff */ };
};

void computation()
{
    Strategy *strategy;
    if ( /* some decision logic */ )
        strategy = new FooStrategy();
    else
        strategy = new BarStrategy();

    //later on...
    //big loop! don't want any overheads beyond what if/else would give
    for (int i=0;i<100000000;++i)
    {
        //...various other code and then:
        strategy->action();
    }

    //...other code and possibly more strategy->action() calls, then finally:
    delete strategy;
}

所有这些都是为了在每次需要 action() 时用决策逻辑替换 if/else 子句而编写的。我们可以假设它读起来更清楚,因为这就是我们使用它的原因。至于开销,strategy 的虚拟函数表无疑最终会出现在处理器缓存中,因此与 if/else 相比,开销应该很小,对吧?

然而。 C++ 不允许堆栈上的抽象基类,大概是因为它们的大小未知,因此策略必须存在于堆上,从而引入了危险的 new 和 删除 运算符。当然,我可以编写一个包装类来安全地处理策略的创建和删除,并且希望编译器能够完全优化它。但我想知道,如果所有策略都具有相同的大小,是否有一种更优雅的方法将策略放入堆栈中,从而确保自动删除?

编辑感谢大家的回复,我认为他们都非常好,并且对不同的做事方式很有启发。我不会接受任何个人的正确观点。我建议未来的读者考虑所有这些,并慷慨地投票!

So I have discovered the joys of strategies.

class Strategy
{
public:
    virtual void action() = 0;
};

class FooStrategy : public Strategy
{
    virtual void action() { /* do some stuff */ };
};

class BarStrategy : public Strategy
{
    virtual void action() { /* do different stuff */ };
};

void computation()
{
    Strategy *strategy;
    if ( /* some decision logic */ )
        strategy = new FooStrategy();
    else
        strategy = new BarStrategy();

    //later on...
    //big loop! don't want any overheads beyond what if/else would give
    for (int i=0;i<100000000;++i)
    {
        //...various other code and then:
        strategy->action();
    }

    //...other code and possibly more strategy->action() calls, then finally:
    delete strategy;
}

All this is written to replace an if/else clause with the decision logic every time I need action(). We can assume it reads more clearly because that's why we're using it. As to overheads, the virtual function table for strategy will doubtless end up in the processor cache, so there should be little overhead compared to if/else, right?

However. C++ doesn't allow abstract base classes on the stack, presumably as their size is unknown, so strategy has to live on the heap, introducing the dangerous new and delete operators. Of course I could write a wrapper class to safely handle the creation and deletion of Strategies, and that would hopefully be optimized away entirely by the compiler. But I wonder if, more elegantly, there is a way to put a Strategy on the stack, thus ensuring automatic deletion, given that all Strategies have the same size?

Edit Thanks for the replies everyone, I think they're all very good and enlightening as to different ways of doing things. I'm going to abstain from accepting any individual one as correct. I recommend any future readers to consider them all and be liberal with upvotes!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

自控 2025-01-05 08:19:57

为什么不直接使用智能指针呢?

std::auto_ptr<Strategy> strategy;
if ( /* some decision logic */ )
    strategy.reset(new FooStrategy());
else
    strategy.reset(new BarStrategy());

现在您无需删除任何内容。

C++11 还提供 std::unique_ptrstd::shared_ptr。此外,Boost 还提供 shared_ptr 以及 boost::scoped_ptr (这比 auto_ptr 更好,因为复制了 auto_ptr code> 可能会导致问题。 scoped_ptr 是不可复制的)。

Why not just use a smart pointer?

std::auto_ptr<Strategy> strategy;
if ( /* some decision logic */ )
    strategy.reset(new FooStrategy());
else
    strategy.reset(new BarStrategy());

Now you have no need to delete anything.

C++11 offers std::unique_ptr and std::shared_ptr as well. Also, Boost offers shared_ptr too, as well as boost::scoped_ptr (which is better than auto_ptr since copying an auto_ptr can cause issues. scoped_ptr is non-copyable).

热风软妹 2025-01-05 08:19:57

如果您可以在通话时间决定,您可以使用 templates:

template <typename TPolicyImpl>
void computation()
{
    TPolicyImpl policy;
 ...
}

并像这样调用它:

  computation<FooPolicy>()

If you can decide that on call time, you could use templates:

template <typename TPolicyImpl>
void computation()
{
    TPolicyImpl policy;
 ...
}

and call it like:

  computation<FooPolicy>()
蓝天 2025-01-05 08:19:57

您可以按照其他人的建议采用智能指针方式,也可以使用 C 风格的方式:

void fooStrategy() { /* do some stuff */ }

void barStrategy() { /* do other stuff */ }

void computation()
{
    typedef void (*Strategy)();
    Strategy strategy;
    if ( /* some decision logic */ )
        strategy = &fooStrategy;
    else
        strategy = &barStrategy;

    //later on...
    //big loop! don't want any overheads beyond what if/else would give
    for (int i=0;i<100000000;++i)
    {
        //...various other code and then:
        strategy();
    }

    //...other code and possibly more strategy() calls
}

编辑

或者,以同样的思路。只要您的策略是无状态的,为什么不为每个策略拥有一个全局对象 - 那么您就不必担心删除:

class FooStrategy : public Strategy
{
    virtual void action() { /* do some stuff */ };
} FooStrategyObject;

class BarStrategy : public Strategy
{
    virtual void action() { /* do different stuff */ };
} BarStrategyObject;

/* ... */

if ( /* some decision logic */ )
    strategy = &FooStrategyObject;
else
    strategy = &BarStrategyObject;

You can go the smart pointer way, as suggested by others, or you can use the C-style way:

void fooStrategy() { /* do some stuff */ }

void barStrategy() { /* do other stuff */ }

void computation()
{
    typedef void (*Strategy)();
    Strategy strategy;
    if ( /* some decision logic */ )
        strategy = &fooStrategy;
    else
        strategy = &barStrategy;

    //later on...
    //big loop! don't want any overheads beyond what if/else would give
    for (int i=0;i<100000000;++i)
    {
        //...various other code and then:
        strategy();
    }

    //...other code and possibly more strategy() calls
}

Edit

Or, in the same line of thought. As long as your strategies are stateless, why not have a single global object for each strategy - then you wouldn't have to worry about deletion:

class FooStrategy : public Strategy
{
    virtual void action() { /* do some stuff */ };
} FooStrategyObject;

class BarStrategy : public Strategy
{
    virtual void action() { /* do different stuff */ };
} BarStrategyObject;

/* ... */

if ( /* some decision logic */ )
    strategy = &FooStrategyObject;
else
    strategy = &BarStrategyObject;
我一向站在原地 2025-01-05 08:19:57

这就是我要做的:

  • 为“Strategy”类和所有其他类创建构造函数 private
  • 编写一个工厂函数,该函数返回指向具有引用计数的 Strategy 类的智能指针或所有权语义的转移。这应该包含您的 if 条件。
  • 让您的每个 Strategy 子类都将此函数作为 friend

这样,您将确保您的Strategy对象仅作为智能指针的一部分创建。缺点是您需要将 friend 声明添加到所有当前和未来的 Strategy 子类中。

Here's what I would do:

  • Make constructors for the 'Strategy' class and all other classes private
  • Write a factory function that returns a smart pointer to a Strategy class with either reference counting or transfer of ownership semantics. This should contain your if condition.
  • Have each of your Strategy subclasses has this function as a friend.

This way, you will ensure that your Strategy objects are only created as parts of a smart pointer. The disadvantage is that you will need to add the friend declaration to all current and future Strategy subclasses.

土豪我们做朋友吧 2025-01-05 08:19:57

您可以使用 ScopeGuard 模式的变体:

const Strategy& strategy = FooStrategy();

const 引用(并且必须是 const)可以绑定到临时变量,从而延长临时变量的生命周期。这使您可以安全(高效)地将所有内容放在堆栈上,同时通过其基本类型(作为 const Strategy& )引用对象。

You can use a variant of the ScopeGuard pattern:

const Strategy& strategy = FooStrategy();

A const reference (and it has to be const) can be bound to a temporary, extending the lifetime of the temporary. This allows you to have everything safely (and efficiently) on the stack, while referring to the object by its base type (as a const Strategy&).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文