【问题标题】:Objects seemingly not being passed by reference in C++在 C++ 中,对象似乎不是通过引用传递的
【发布时间】:2020-05-21 09:00:28
【问题描述】:

编辑:已发现问题是 Wrapper 类在传递到 MCEngine 函数和下面所示的 SimulationEngine 类时正在创建 BSCallFunction 的副本。考虑到这一点,我仍然需要弄清楚如何让程序做我想做的事情,如问题中所述。评论部分包含迄今为止的发现。

class SimulationEngine
{
public:
    SimulationEngine(double horizon, const Wrapper<valuationFunction>& theFunction_, RiskFactor simulatedRiskFactor);
    virtual void DoOnePath(double vol, double normvariate) = 0;
    virtual SimulationEngine* clone() const = 0;
    const double GetHorizon();
    Wrapper<valuationFunction>& GetFunction();
    RiskFactor simulatedRiskFactor;
protected:
    double horizon;
    Wrapper<valuationFunction> theFunction;
};

在我的主类中,我正在创建这个对象:

    BSCallFunction ThisBsCall(nominal, S0, r, d, impvol, TTM, Strike);

我将其传递给两个类(派生自同一个基类),这将改变它的一些值(如 S0 和 r)。

    OneStepBSEngine BSSimulation(timeHorizon, zeroDrift, ThisBsCall, RiskFactor::equity);
    OneStepBrownianMotionEngine ShortRateSimulation(timeHorizon, zeroDrift, ThisBsCall, RiskFactor::interest_rate);

我希望这两个类都更改同一个对象的值,以便最终得到一个具有不同 r 和 S0 的 ThisBsCall。

类的结构如下:

class OneStepBrownianMotionEngine : public SimulationEngine
{
public:
    OneStepBrownianMotionEngine(double horizon_, double drift_,const Wrapper<valuationFunction>& theFunction_, RiskFactor simulatedRiskFactor_);
    virtual SimulationEngine* clone() const;
    void DoOnePath(double vol, double normvariate);
private:
    double drift;
};

OneStepBrownianMotionEngine::OneStepBrownianMotionEngine(double horizon_, double drift_,const Wrapper<valuationFunction>& theFunction_, RiskFactor simulatedRiskFactor_) : SimulationEngine(horizon_, theFunction_, simulatedRiskFactor_), drift(drift_)
{
}

我通过 const 引用传递“ThisBsCall”对象。

回到我的主类,然后我将创建对象 MCEngine 并使用将使用上面的 Engine 类更改 r 和 S0 的值的函数,Enginevector 只是两个 Engine 类的向量:

MCEngine VAREngine(EngineVector, covmat);
VAREngine.DoSimulation(gathererCombiner, NumberOfPaths);

我也像这样通过 const 引用传递 enginevector:

MCEngine(const std::vector<Wrapper<SimulationEngine>>& EngineVector, std::vector<std::vector<double>> covMatrix_);

我不确定这是否重要,但使用的构造函数和函数在 MCEngine 类中如下所示:

MCEngine::MCEngine(const std::vector< Wrapper<SimulationEngine>>& EngineVector_, std::vector<std::vector<double>> covMatrix_)
    : cholMatrix(Cholesky_Decomposition(covMatrix_)), EngineVector(EngineVector_), V(0)
{
}

void MCEngine::DoSimulation(StatisticsMC& TheGatherer, unsigned long NumberOfPaths)
{
    UpdateTTM();  
    double thisPortfolioValue;
    for (unsigned long i = 0; i < NumberOfPaths; ++i)
    {
        std::vector<Wrapper<SimulationEngine>> tmpEngineVector(EngineVector);  //save the origianl EngineVector to use on next path
        //create vector of correlated normal variats with cholezky decompositon
        MJArray corrNormVariates(cholMatrix.size());
        corrNormVariates = 0;
        MJArray NormVariates(cholMatrix.size());
        for (unsigned long i = 0; i < cholMatrix.size(); i++)
        {
            NormVariates[i] = GetOneGaussianByBoxMuller();
            for (unsigned long j = 0; j < cholMatrix[i].size(); j++) {
                corrNormVariates[i] += cholMatrix[i][j] * NormVariates[j];
            }
            corrNormVariates[i] /= cholMatrix[i][i]; //normalize the random variates
        }
        //use each one for simulation with spotvector/volvector
        for (unsigned long j = 0; j < EngineVector.size(); ++j)
        {
            EngineVector[j]->DoOnePath(cholMatrix[j][j], corrNormVariates[j]); //updates the riskfactors for the positions
        }
        ValuePortfolio();
        thisPortfolioValue = GetPortfolioValue();
        TheGatherer.DumpOneResult(thisPortfolioValue);
        EngineVector = tmpEngineVector;
    }
}

现在我的问题是,当我查看 2 个 BSCallFunction 对象(我希望它们是相同的 ThisBsCall)时,它们的 S0 和 r 具有不同的值,所以似乎在某些时候我复制了对象并更改了r 或 S0 这个副本而不是原来的然后返回结果。是否有可能从我发布的内容(或关于我可能做错的一般建议)中看到这可能发生在哪里?如果有帮助,我可以发布更多代码。

编辑:包装类以防万一这可能是导致问题的原因:

#ifndef WRAPPER_H
#define WRAPPER_H
template< class T>
class Wrapper
{
public:
    Wrapper()
    {
        DataPtr = 0;
    }
    Wrapper(const T& inner)
    {
        DataPtr = inner.clone();
    }
    Wrapper(T* DataPtr_)
    {
        DataPtr = DataPtr_;
    }
    ~Wrapper()
    {
        if (DataPtr != 0)
            delete DataPtr;
    }
    Wrapper(const Wrapper<T>& original)
    {
        if (original.DataPtr != 0)
            DataPtr = original.DataPtr->clone();
        else
            DataPtr = 0;
    }
    Wrapper& operator=(const Wrapper<T>& original)
    {
    if (this != &original)
    {
    T* newPtr = (original.DataPtr != 0) ?
    original.DataPtr->clone() : 0;
    if (DataPtr != 0)
    delete DataPtr;
    DataPtr = newPtr;
    }
    return *this;
    }

        T& operator*()
    {
        return *DataPtr;
    }
    const T& operator*() const
    {
        return *DataPtr;
    }
    const T* const operator->() const
    {
        return DataPtr;
    }
    T* operator->()
    {
        return DataPtr;
    }
private:
    T* DataPtr;
};
#endif

【问题讨论】:

  • 看起来您传递的是 const Wrapper&lt;BSCallFunction&gt; &amp; 而不是 const BSCallFunction &amp; 如果为每次调用构建一个临时对象,这可能解释了您所看到的差异。
  • 你的意思是它可能是因为 Wrapper 类而构建的吗?我和班级一起编辑了帖子,你能看看是不是这样?
  • 包装类正在克隆对象。见Wrapper(const T&amp; inner)
  • 有简单的解决方案吗?我自己没有编写 Wrapper 类,所以我不确定它是如何工作的,它似乎只是为了满足我的目的
  • 如果包装类使用指针构造函数,它不会复制而是使用原始对象。 OneStepBSEngine BSSimulation(timeHorizon, zeroDrift, &amp;ThisBsCall, RiskFactor::equity); 老实说,在尝试改变之前,你需要了解这个类在做什么。

标签: c++ reference const-reference


【解决方案1】:

您认为Wrapper 类正在创建BSCallFunction 对象的多个副本是正确的。

例如:

BSCallFunction ThisBsCall(nominal, S0, r, d, impvol, TTM, Strike);
OneStepBrownianMotionEngine ShortRateSimulation(timeHorizon, zeroDrift, ThisBsCall, RiskFactor::interest_rate);

在上面的行中,创建了一个临时的Wrapper 对象并将其传递给OneStepBrownianMotionEngine 的构造函数,它会克隆内部对象并存储指向它的指针。然后这个克隆的对象被传递给基类SimulationEngine,它使用Wrapper类的复制构造函数存储它的副本。

所以 1 个临时对象 + 另一个副本。

这同样发生在另一个引擎类OneStepBrownianMotionEngine

现在我的问题是当我查看 2 个 BSCallFunction 对象(我 期望是相同的 ThisBsCall)它们对 S0 有不同的值 和 r,所以似乎在某些时候我复制了对象并更改了 此副本的 r 或 S0 的值而不是原始值,然后 返回结果。

它们永远不会相同,因为Wrapper 类旨在克隆传递给它的对象。您可以在堆 (new BSCallFunction(...)) 上创建 ThisBsCall 并使用指针。这将确保两个引擎使用相同的ThisBsCall

但是,正如您所发现的,当引擎被销毁时,这将导致一个问题:Wrapper 的析构函数试图删除相同的ThisBsCall 对象。双重删除总是会导致崩溃!

你对你的Wrapper有什么好处:

  • 使用指向ThisBsCall 的指针,这样您就不会复制它
  • Wrapper 对象的每个共享所有者维护一个引用计数
  • 当引用计数降至零时销毁 ThisBsCall 对象

std::shared_ptr 为您完成所有这些工作,它是标准库的一部分。所以我建议你使用它而不是你的 Wrapper 类。它将需要最少的更改。

您的代码可以保持不变,除非您替换

Wrapper<your_class_name>

std::shared_ptr<your_class_name> 

例如:

std::shared_ptr<valuationFunction> ThisBsCall = std::make_shared<BSCallFunction>(nominal, S0, r, d, impvol, TTM, Strike);

smart pointersstd::shared_ptrstd::unique_ptr 一样的美妙之处在于它们会为您处理内存管理。您不必担心手动调用newdelete。所有这些都是在后台为您完成的。此外,它们也为您提供异常安全性。如果在内存分配期间由于某种原因引发异常,则会为您清理内存。由于这些原因以及更多原因,这就是为什么现在推荐的做法是使用智能指针而不是自己进行内存管理。

我不知道您是否在多线程环境中编码?尽管存储托管对象和引用计数器的控制块是线程安全的,但智能指针本身不是。您仍然需要提供自己的线程同步。

【讨论】:

  • 非常感谢,我以前听说过智能指针,但从未使用过它们,但这绝对是有道理的。现在考虑一下,我猜 Wrapper 类在某种程度上是它自己的智能指针?你认为我应该尝试在我的代码中完全摆脱 Wrapper 类以“现代化”它吗?即,是否有任何情况下您会使用类似的东西而不是 STL 智能指针之一?我用来了解这方面的书是 2002 年写的,所以在这个主题上可能有点过时了。老实说,我不知道什么是多线程环境。
  • @Oscar 对于在两个引擎中使用相同的BSCallFunction 对象的预期目的,std::shared_ptr 是理想的。设计Wrapper 类的人希望确保他们使用的是原始对象的克隆。因此,您遇到的问题。这是一个很好的例子,说明了为什么应该使用智能指针。它们随着 C++11 进入 C++ 标准。在此之前,您要么自己实现一个,要么使用boost::shared_ptr。如果您自己实现它并且没有正确执行,那么您可能会遇到很多内存问题。
  • 谢谢你,我觉得这对我来说可能是一个很好的学习体验,尝试在我的程序中摆脱对 Wrapper 的其他提及,并找到合适的智能指针来做同样的事情
猜你喜欢
  • 2011-04-19
  • 2021-10-14
  • 2018-08-17
  • 2013-08-11
  • 2011-07-06
  • 1970-01-01
相关资源
最近更新 更多