1

我有一个基类,令牌。它没有实现,因此充当标记接口。这是调用者将使用的类型。

{
    Token t = startJob(jobId);
    // ... (tasks)
    // t falls out of scope, destructors are called
}

我有一个派生类 LockToken。它包裹了一个互斥体,并确保在构造过程中获取锁并在销毁过程中释放锁。startJob 方法在某种意义上是工厂方法,它决定是返回 Token(不提供锁定)还是 LockToken(提供锁定)。

Token startJob(int jobId)
{
    return (jobId>0) ? LockToken() : Token() ;
}

当 startJob 将返回一个基本实例(一个令牌)时,一切正常。在另一种情况下(jobId>0),将派生实例复制到基本实例。在其他工作中,从 LockToken 复制构造了一个不同的 Token,原始 LockToken 过早地超出范围,释放 startJob 范围内的锁。

我该如何摆脱困境?我可以更改 startJob 以使其返回或输出真正的协变令牌(意味着它可能是 LockToken)?

4

5 回答 5

9

您正在按值返回 Token。这意味着您不会返回 LockToken,而是从 LockToken 实例构造的 Token 副本。

更好的方法是使用 boost::shared_ptr。这样,您的客户可以复制内容而无需担心删除。像这样的东西:

#include <boost/shared_ptr.hpp>

typedef boost::shared_ptr<void> Token;

Token startJob(int jobId)
{
    if (jobId < 1) return shared_ptr<void>();
    return shared_ptr<void>(new LockToken);
}

void use_it()
{
    Token t = startJob(jobId);
    // ....
    // Destructors are called
}

请注意,您不再需要什么都不做的 Token 类,LockToken 类现在是一个对客户端完全隐藏的实现细节,当 Token 超出范围时,您可以进行各种其他事情。

于 2009-10-15T06:09:41.420 回答
5

您应该从 startJob() 返回一个指针 - 原始指针或合适的智能指针。例如:

Token* startJob(int jobId) 
{ 
    return (jobId>0) ? new LockToken() : new Token(); 
} 

{ 
    std::auto_ptr<Token> t = startJob(jobId); 
    // ... (tasks) 
    // t falls out of scope, destructors are called 
}

auto_ptr超出范围时,它会调用delete包装的指针。

上述解决方案是您的直接重写。正如这个问题的另一个答案中提到的,你根本不需要虚拟Token类 - 你可以只返回一个空指针:

LockToken* startJob(int jobId) 
{ 
    return (jobId>0) ? new LockToken() : 0; 
} 

{ 
    std::auto_ptr<LockToken> t = startJob(jobId); 
    // ... (tasks) 
    // t falls out of scope, destructors are called 
}

auto_ptr可以安全地分配一个空指针——它的析构函数会处理这个问题。

于 2009-10-15T05:57:33.567 回答
2

一种相当典型的方法是使用指针在堆上声明您的 Token / LockToken 对象。

Token* startJob(int jobID)
{
    Token* t;
    if (jobID >0)
    {
        t = new LockToken();
    }
    else
    {
        t = new Token();
    }

    return t;
}

当然,当你完成返回值时,你必须负责删除它。或者,您可以使用智能指针来管理自己的销毁。

于 2009-10-15T05:58:25.907 回答
1

在 C++ 中,要获得多态行为,您需要使用指针或引用。在您的特定情况下,由于Token必须超出函数的生命周期,startJob您不能将引用返回到内部堆栈分配的对象中,因为在使用位置(调用者startJob)它将是一个悬空引用。

因此,您只剩下动态分配的内存,此时您可以选择如何处理堆分配的对象生命周期。我建议不要使用原始指针,因为它们本质上是异常不安全的,使用原始指针作为返回值并在智能指针中管理指针或已经返回智能指针已经有不同的好答案。

返回原始指针并在智能指针中对其进行外部管理的缺点是它对于用户代码来说更加脆弱。调用者可以使用智能指针,也可以使用原始指针(或忽略返回的对象),它会丢失内存。在用户界面中使用shared_ptr会在调用者代码中强制使用该智能指针(用户无法决定更改为另一种智能指针类型)。

在这一点上,使用旧的std::auto_ptr作为返回类型似乎是最灵活的方法:

std::auto_ptr<Token> startJob( int jobId );

void user_code()
{
   std::auto_ptr<Token> job1 = startJob(1);
   boost::shared_ptr<Token> job2( startJob(2) ); // shared_ptr has a constructor taking auto_ptr
   startJob(3); // fine: the temporary auto_ptr dies and releases the memory
   boost::scoped_ptr<Token> job4( startJob(4).release() ); // cumbersome, but feasible
}

scoped_ptr参考)

如果返回的类型是另一种类型的智能指针作为返回类型,那么您将无法使其产生用于另一种类型的智能指针的资源。如果您返回原始指针作业 3 令牌将不会被释放。

我没有考虑unique_ptr进行讨论。它似乎是 auto_ptr 的一个很好的替代品,但我从未使用过它,所以我无法从经验中判断。

于 2009-10-15T06:44:22.560 回答
0

感谢所有的答复!我选择使用 janm 的解决方案,但我将使用 astd::auto_ptr而不是boost::shared_ptr. 请注意,这使得该解决方案也类似于Sharptooth 的答案(带有Matthieu M 的评论)。我已经对其进行了原型设计,并将其与应用程序的其余部分集成在一起,并且很高兴地报告它运行良好。

于 2009-10-15T23:39:14.653 回答