Linux和Windows之间返回值差异的原因是什么以及如何解决它



这是我尝试返回类对象的代码。 但我从 CentOs (gcc) 和 Visual Studio 2013 得到了不同的结果。在 cls.cpp 中,使用 gcc,它运行良好,我得到的结果是 detector.name = "t_name",detector.stride = 5。但检测器中的值在 VS2013 下为 " 和 0。似乎在 vs2013 中解构了 cls 对象。为什么我得到了不同的回报?以及如何使其在Visual Studio下运行良好?多谢。

CLS.H

#pragma once
#include <iostream>
#include <string>
#define OUT(x)  do{ std::cout << x << std::endl;}while(0)
template <class T> class IBuilder{
public:
    virtual ~IBuilder(){};
    virtual T build() = 0;
};
class cls
{
public:
    ~cls();
    cls(const cls& origin);
    class Builder : public IBuilder<cls>{
    private:
        std::string _name;
        int _stride = 4;        
        double _cascThr = -1;
    public:
        Builder(const std::string name);
        ~Builder();
        Builder* stride(int s);
        Builder* cascThr(double t);
        cls build();
        friend cls;
    };
private:
    std::string _name;
    int _stride;
    double _cascThr;
    Builder* _builder;
    cls(Builder* builder);
    cls& operator=(const cls&);//prevent the compiler to generate copying assignment
};

CLS.cpp

#include "cls.h"
using namespace std;
cls::cls(const cls& origin){}
cls::cls(cls::Builder* builder) {
    this->_builder = builder;
    OUT("cls(Builder*)");
    this->_name = builder->_name;   
    this->_stride = builder->_stride;   
    this->_cascThr = builder->_cascThr; 
}
cls::   ~cls(){ 
    OUT("~cls()");
}
cls::Builder::Builder(const string name){
    OUT("Builder(string)");
    this->_name = name;
}
cls::Builder::~Builder(){
    OUT("~Builder() ");
}
cls::Builder* cls::Builder::stride(int s){
    this->_stride = s;
    return this;
}
cls::Builder* cls::Builder::cascThr(double t){
    this->_cascThr = t;
    return this;
}
cls cls::Builder::build(){
    OUT("Build ACF Detector From Builder");
    return cls(this);
}

主.cpp

#include "cls.h"
using namespace std;
cls func(){
    cls::Builder* builder = NULL;
    builder = new cls::Builder("t_name");
    builder->stride(5);
    builder->cascThr(1.0);
    cls detector = builder->build();
    return detector;
}
int _tmain(int argc, _TCHAR* argv[])
{
    cls tt = func(); // here I got different answers.
    return 0;
}

你有一个不执行任何操作的复制构造函数。

cls::cls(const cls& origin){}

GCC 可能确实在 cls tt = func();cls detector = builder->build(); 行中复制了省略号,并且它神奇地工作。也许一旦您启用优化,VS 也会这样做。就个人而言,我会说这是您代码中的一个错误。如果您有复制构造函数,则复制源对象。

我使用 Visual Studio 2015 尝试了您的代码,发现析构函数 ~cls() 执行了两次。对我来说,这看起来像一个错误,但我不是专家。

你说的"解构"是指"被摧毁"吗?

我在 Windows 10 64 位下编译了使用 Gnu g++ 编译的代码,在 cygwin 下。这次析构函数只运行了一次。

我似乎记得当一个函数构造一个对象并返回它时(就像你的"cls tt = func();"),编译器可能会选择在最终位置构造对象,即在调用函数的堆栈帧上。我不知道这种行为是否是强制性的。

现在我又试一次...在Visual Studio中,我在OUT("~cls()"的行上设置了一个断点;"(.cpp:12)。第一次命中断点时,堆栈跟踪为:

cls::~cls() (line 12)
func() (line 9, "return detector;")
main() (line 13, "cls tt = func();")

在调试器中单击"继续",再次命中断点。现在堆栈是:

cls::~cls() (line 12)
main() (line 14, "return 0").

因此,Visual Studio 似乎确实构造并销毁了 func() 的堆栈帧中的对象,将对象复制到 main() 的堆栈帧中,在 func() 退出时在"tt"上调用析构函数,并在 main() 退出时在第二个实例上再次调用析构函数。 没关系,赋值运算符是私有的,复制构造函数是一个空函数。

不幸的是,我不是专家,不能说这与标准相比如何。

你说使用一个编译器,"检测器"的值是"t_name"和5,但使用Visual Studio,值是"和0。(你怎么知道?代码不输出值,所以我想你的代码中可能有更多的 OUT......

我的猜测是编译器使用了空的复制构造函数(cls.cpp第 3 行),因此 main() 的堆栈帧("检测器")中的副本没有将数据复制到其中。也许教训是,你不能仅仅通过否认Visual Studio的行为愚蠢来强迫它聪明地行事。如果 VS 没有能力在 main() 的 "detector" 变量中构造 func() 的 "tt" 变量,那么当你锁定赋值运算符时,它不会勉强获得这种能力。

也许我们都应该阅读何时使用赋值运算符,以及何时使用复制构造函数。似乎Visual Studio解释了你的代码

cls tt = func();

好像是

cls tt( func() );

使用复制构造函数初始化 TT。也许赋值运算符仅在目标为已正确初始化的对象时才使用。

最新更新