堆叠对象的C ++继承

时间:2009-10-15 05:37:06

标签: c++ inheritance locking stack mutex

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

{
    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),有一个由派生实例组成的副本到基础实例。在其他工作中,一个不同的Token是从LockToken复制构造的,并且原始的LockToken过早地超出了范围,在startJob的范围内释放了锁。

我如何摆脱这种局面?我可以更改startJob以使其返回或输出真正的协变令牌(意味着它可能是一个LockToken)吗?

5 个答案:

答案 0 :(得分:9)

您将按值返回令牌。这意味着你没有返回一个LockToken,而是一个从你的LockToken实例构造的令牌副本。

更好的方法是使用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超出范围时执行各种其他操作。

答案 1 :(得分: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可以安全地分配一个空指针 - 它的析构函数将处理这个。

答案 2 :(得分:2)

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

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

    return t;
}

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

答案 3 :(得分: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的一个很好的替代品,但我从未使用它,所以我无法从经验中看出来。

答案 4 :(得分:0)

感谢所有回复!我已选择使用janm的解决方案,但我将使用std::auto_ptr而不是boost::shared_ptr。请注意,这使解决方案也类似于sharptooth的答案(与Matthieu M的评论)。我已将其原型化,并将其与应用程序的其余部分集成,并很高兴地报告它运行良好。