字符串连接和内存管理

时间:2013-09-26 07:42:13

标签: c++ memory-management

我已经使用C ++一段时间了,至少还有一件我无法理解的事情,也无法在网上找到一个好的解释。它与内存管理有关,可以用这个例子说明:

考虑std :: string的字符串连接运算符,它看起来像这个

  

std :: string operator +(const string& s1,string& s2)

,正如大家所知,它返回一个新创建的对象(另一个std :: string),其中包含连接的两个原始字符串。 我的问题是:这怎么可能?这个对象在内存中的位置?

我的意思是,如果我必须编写函数实现,我会做这样的事情

std::string std::string::operator+(const string& s1, string& s2)
{
std::string *res=new std::string;
// fill res with the content of s1 and s2
return *res;
}

但是通过这种方式,我知道我会导致内存泄漏,因为如果我调用该函数一百万次,我将生成一百万个字符串,在程序结束之前不会解除分配。另一方面,我可以做到这一点:

std::string& std::string::operator+(const string& s1, string& s2)
{
std::string res;
// fill res with the content of s1 and s2
return res;
}

但是这样我会返回一个对局部变量的引用,一旦函数返回就变成垃圾。 最后我可以简单地写一下

std::string std::string::operator+(const string& s1, string& s2)
{
std::string res;
// fill res with the content of s1 and s2
return res;
}

并按值传递结果,这应该完成任务,但在我看来效率非常低,因为我必须将整个res(理论上可能非常大)对象复制到调用函数。 我之所以这么说是因为我实际上正在研究一个线性代数库,那么执行它就非常好了。矩阵加法只是

m3=m1+m2;

就像字符串连接一样,但如果唯一的解决方案是复制生成的对象,那么使用双矩阵(例如100MB)是不实际的。目前我使用的功能是

matrix& matrix::sum(matrix& m1, matrix& m2)

以这种方式使用

m3.sum(m2,m1);

看起来很难看,也阻止我在一行中对多个矩阵求和,我必须写

m4.sum(m1,m2)
m4.sum(m4,m3)

(m4.sum(m1,m2)).(m4,m3)

如果我真的想在单行中制作它,但它绝对不可读。

有没有更好的方法来做所有这些事情?

提前致谢

3 个答案:

答案 0 :(得分:4)

这个版本是正确的

std::string std::string::operator+(const string& s1, string& s2)
{
    std::string res;
    // fill res with the content of s1 and s2
    return res;
}

大多数编译器采用称为命名的返回值优化的优化技术来处理复制值的低效率。标准明确允许,称为 copy elision

在C ++ 11中还有另一种方法:当你返回字符串res时,它变成一个r值,而移动构造函数将被用来代替复制构造函数,这也很便宜。但同样,大多数编译器都会优化复制和移动。

最后,我不知道为什么你必须自己实现一个矩阵库。如果不是作业,请改用Eigen。优化矩阵代数是一项非常艰苦的工作,需要大量的低层次理解。

答案 1 :(得分:3)

现代编译器将执行"复制elision",这几乎意味着您的最后一个字符串示例实际上没有复制结果字符串,它只是将结果存储在由调用代码。这同样适用于您自己设计的vectormatrix

答案 2 :(得分:3)

正如已经指出的那样,在超载的情况下 operator+,你必须返回一个完整的对象(按值)。 一般来说,这不是人们想象的问题;事 像RVO一样使它更不成问题。在(大)的情况下 另一方面,矩阵可能成为一个严重的问题,而不是 仅仅因为运行时,但是由于内存的考虑; 如果你有一个表达式:

m = m1 + m2 + m3 + m4 + m5;
将有四个临时工,他们都将持续到 结束了完整的表达。如果矩阵很大,那就可以 施加重大记忆压力。在这种情况下,通常 技术是返回一些特殊的类型,它只是保留 指向左手和右手参数的指针; operator=(和 一个构造函数)然后重载以获取此类型,并构建 动态的最终矩阵。类似的东西:

class MatrixProxy
{
    void* operator new( size_t );   //  Prevent dynamic allocation
public:
    virtual int rows() const = 0;
    virtual int columns() const = 0;
    virtual double get( int row, int column ) const = 0;
};

class MatrixOpAddResults : public MatrixProxy
{
    MatrixProxy const* lhs;
    MatrixProxy const* rhs;
public:
    MatrixOpAddResults( Matrix const& lhs, Matrix const& rhs )
        : lhs( &lhs )
        , rhs( &rhs )
    {
        assert( lhs->rows() == rhs->rows() && lhs->columns() == rhs->columns() );
    }
    int rows() const override
    {
        return lhs->rows();
    }
    int columns() const override
    {
        return lhs->columns();
    }
    double get( int row, int column ) const override
    {
        return lhs->get( row, column ) + rhs->get( row, column );
    }
};

MatrixProxy operator+( MatrixProxy const& lhs, MatrixProxy const& rhs )
{
    return MatrixProxy( lhs, rhs );
}

然后,例如......

Matrix::Matrix( MatrixProxy const& other )
    : m_rows( other.rows() )
    , m_columns( other.columns() )
    , m_data( other.rows() & other.columns() )
{
    std::vector<double>::const_iterator dest = m_data.begin();
    for ( int i = 0; i != m_rows; ++ i ) {
        for ( int j = 0; j != m_columns; ++ j ) {
            *dest = other.get( i, j );
            ++ dest;
        }
    }
}

当然,Matrix本身应该从MatrixProxy派生而来 好。你需要为每个操作员提供一个Results类。

现代趋势是使用模板,而不是 继承,在这里。我找到了基于继承的解决方案 然而,更清晰,更容易理解(因为它更多 显式),至少用于解释技术,以及两者 应该最终产生完全相同的代码(提供全部 结果类中的函数是内联的。

最后:除非这是为了个人理解,否则有 几个可以免费获得的实现矩阵的好库 使用上述技术。 (突然想到了闪电战++,但是 我不知道它目前的状况。)