字符串串联和内存管理

String concatenation and memory managment

本文关键字:内存 管理 字符串      更新时间:2023-10-16

我已经使用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)

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

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

提前致谢

这个版本是正确的

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

大多数编译器采用一种称为"返回值优化"的优化技术来处理复制值的低效率问题。这是标准明确允许的,称为复制省略

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

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

正如已经指出的那样,在像重载这样的情况下 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,如井。 每个运算符都需要一个 Result 类。

现代趋势是使用模板,而不是继承,在这里。 我找到了基于继承的解决方案然而,更清晰、更易于理解(因为它更明确(,至少用于解释技术,以及两者兼而有之最终应该生成完全相同的代码(前提是所有结果类中的函数是内联的(。

最后:除非这是出于个人理解,否则有免费提供几个实现矩阵的好库使用上述技术。 (闪电战++浮现在脑海中,尽管我不知道它的当前状态是什么。

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