c++ - C++ 容器中的逻辑常量

标签 c++ stdmap mutable const-iterator

进行编辑以包含 MWE(删除 example-lite)并添加有关编译和 Valgrind 输出的详细信息。

我使用 mutable 关键字来实现延迟求值和缓存结果的结果。这对于单个对象来说效果很好,但对于集合来说似乎并不像预期的那样工作。

我的情况比较复杂,但假设我有一个三角形类,可以计算三角形的面积并缓存结果。在我的例子中,我使用指针,因为被延迟评估的东西是一个更复杂的类(它实际上是同一类的另一个实例,但我试图简化这个示例)。

我有另一个类,它本质上是三角形的集合。它有一种方法可以计算所有包含的三角形的总面积。

从逻辑上讲,tri::Area() 是 const,而 mesh::Area() 是 const。当按上述方式实现时,Valgrind 显示内存泄漏 (m_Area)。

我相信,由于我使用的是 const_iterator,所以对 tri::Area() 的调用作用于三角形的拷贝。在该拷贝上调用 Area(),该拷贝执行新操作、计算面积并返回结果。此时,拷贝丢失并且内存泄漏。

此外,我相信这意味着该区域实际上并未被缓存。下次我调用 Area() 时,它会泄漏更多内存并再次进行计算。显然,这是不理想的。

一种解决方案是使 mesh::Area() 成为非 const。这不太好,因为它需要从其他 const 方法调用。

我认为这可能有效(将 m_Triangles 标记为可变并使用常规迭代器):

但是,我不喜欢将 m_Triangles 标记为可变 - 我更愿意保持编译器在其他不相关的方法中保护 m_Triangles 的一致性的能力。因此,我很想使用 const_cast 将丑陋的内容本地化为需要它的方法。像这样的事情(可能会出现错误):

不确定如何使用 const_cast 来实现——我应该转换 m_Triangles 还是这个?如果我转换这个,m_Triangles 是否可见(因为它是私有(private)的)?

我还缺少其他一些方式吗?

我想要的效果是保持 mesh::Area() 标记为 const,但调用它会导致所有 tris 计算并缓存它们的 m_Area。当我们这样做时——没有内存泄漏,Valgrind 很高兴。

我发现了很多在对象中使用可变的示例——但没有找到关于在另一个对象的集合中使用该对象的示例。指向这方面的博客文章或教程文章的链接会很棒。

感谢您的帮助。

更新

从这个 MWE 来看,我对泄漏点的判断似乎是错误的。

如果删除了对 SplitIndx() 的调用,下面的代码就是 Valgrind 干净的。

此外,我添加了一个简单的测试来确认缓存的值正在容器存储的对象中存储和更新。

现在看来,调用m_Triangles[indx] = t1;是发生泄漏的地方。我该如何堵住这个漏洞?

#include <cmath>
#include <map>
#include <cstdio>


class point
{
public:
    point()
    {
        v[0] = v[1] = v[2] = 0.0;
    }
    point( double x, double y, double z )
    {
        v[0] = x; v[1] = y; v[2] = z;
    }
    double v[3];
    friend point midpt( const point & p1, const point & p2 );
    friend double dist( const point & p1, const point & p2 );
    friend double area( const point & p1, const point & p2, const point & p3 );
};

point midpt( const point & p1, const point & p2 )
{
    point pmid;
    pmid.v[0] = 0.5 * ( p1.v[0] + p2.v[0] );
    pmid.v[1] = 0.5 * ( p1.v[1] + p2.v[1] );
    pmid.v[2] = 0.5 * ( p1.v[2] + p2.v[2] );
    return pmid;
}

double dist( const point & p1, const point & p2 )
{
    double dx = p2.v[0] - p1.v[0];
    double dy = p2.v[1] - p1.v[1];
    double dz = p2.v[2] - p1.v[2];
    return sqrt( dx * dx + dy * dy + dz * dz );
}

double area( const point & p1, const point & p2, const point & p3 )
{
    double a = dist( p1, p2 );
    double b = dist( p1, p3 );
    double c = dist( p2, p3 );

    // Place in increasing order a, b, c.
    if ( a < b )
    {
        std::swap( a, b );
    }
    if ( a < c )
    {
        std::swap( a, c );
    }
    if ( b < c )
    {
        std::swap( b, c );
    }

    if ( c-(a-b) < 0.0 )
    {
        // Not a real triangle.
        return 0.0;
    }

    return 0.25 * sqrt( ( a + ( b + c ) ) * ( c - ( a - b ) ) * ( c + ( a - b ) ) * ( a + ( b - c ) ) );
}

class tri
{
public:
    tri()
    {
        m_Area = NULL;
    }
    tri( const point & p1, const point & p2, const point & p3 )
    {
        m_P1 = p1; m_P2 = p2; m_P3 = p3;
        m_Area = NULL;
    }
    ~tri() {
        delete m_Area;
    }
    tri( const tri & t )
    {
        m_P1 = t.m_P1;
        m_P2 = t.m_P2;
        m_P3 = t.m_P3;
        if ( t.m_Area )
        {
            m_Area = new double( *(t.m_Area) );
        }
        else
        {
            m_Area = NULL;
        }
    }
    tri & operator=( const tri & t )
    {
        if ( this != &t )
        {
            m_P1 = t.m_P1;
            m_P2 = t.m_P2;
            m_P3 = t.m_P3;
            if ( t.m_Area )
            {
                m_Area = new double( *(t.m_Area) );
            }
            else
            {
                m_Area = NULL;
            }
        }
        return *this;
    }
    bool KnowsArea() const
    {
        if ( !m_Area ) return false;
        return true;
    }
    void SetPts( const point & p1, const point & p2, const point & p3 )
    {
        m_P1 = p1; m_P2 = p2; m_P3 = p3;
        delete m_Area;
        m_Area = NULL;
    }
    double Area() const
    {
        if ( !m_Area )
        {
            m_Area = new double;
            *m_Area = area( m_P1, m_P2, m_P3 );
        }
        return *m_Area;
    }
    void Split( tri & t1, tri & t2 )
    {
        point p4 = midpt( m_P2, m_P3 );
        t1.SetPts( m_P1, m_P2, p4 );
        t2.SetPts( m_P1, p4, m_P3 );
    }

private:
    point m_P1;
    point m_P2;
    point m_P3;
    mutable double * m_Area;
};

class mesh
{
public:
    double Area() const
    {
        double area = 0;
        std::map<int,tri>::const_iterator it;
        for (it=m_Triangles.begin(); it!=m_Triangles.end(); ++it)
        {
            area += it->second.Area();
        }
        return area;
    }
    std::map<int, tri> m_Triangles;

    int KnownArea() const
    {
        int count = 0;
        std::map<int,tri>::const_iterator it;
        for (it=m_Triangles.begin(); it!=m_Triangles.end(); ++it)
        {
            if ( it->second.KnowsArea() ) count++;
        }
        return count;
    }

    void SplitIndx( int indx )
    {
        tri t1, t2;
        m_Triangles[indx].Split( t1, t2 );
        m_Triangles[indx] = t1;
        m_Triangles[m_Triangles.size()+1] = t2;
    }

    int NumTri() const
    {
        return m_Triangles.size();
    }
};



int main( void )
{
    point p1( 0, 0, 0 );
    point p2( 1, 0, 0 );
    point p3( 0, 1, 0 );
    point p4( 1, 1, 0 );
    point p5( 3, 4, 0 );

    tri t1( p1, p2, p3 );
    tri t2( p1, p2, p4 );
    tri t3( p1, p3, p4 );
    tri t4( p1, p3, p5 );
    tri t5( p1, p4, p5 );

    mesh m;
    m.m_Triangles[1] = t1;
    m.m_Triangles[2] = t2;
    m.m_Triangles[3] = t3;
    m.m_Triangles[4] = t4;
    m.m_Triangles[5] = t5;

    printf( "Known areas before total %d of %d\n", m.KnownArea(), m.NumTri() );

    double area = m.Area();

    printf( "Total area is %f\n", area );

    printf( "Known areas after total %d of %d\n", m.KnownArea(), m.NumTri() );

    printf( "Splitting\n" );

    m.SplitIndx( 3 );

    printf( "Known areas before total %d of %d\n", m.KnownArea(), m.NumTri() );

    area = m.Area();

    printf( "Total area is %f\n", area );

    printf( "Known areas after total %d of %d\n", m.KnownArea(), m.NumTri() );

    return 0;
}

编译:

clang++ -Wall -std=c++11 -stdlib=libc++ mwe.cpp -o mwe

或者:

g++ -Wall -std=c++11 mwe.cpp -o mwe

Valgrind 输出(来自 clang):

$ valgrind --track-origins=yes --leak-check=full ./mwe
==231996== Memcheck, a memory error detector
==231996== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==231996== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==231996== Command: ./mwe
==231996==
Known areas before total 0 of 5
Total area is 3.500000
Known areas after total 5 of 5
Splitting
Known areas before total 4 of 6
Total area is 3.500000
Known areas after total 6 of 6
==231996==
==231996== HEAP SUMMARY:
==231996==     in use at exit: 8 bytes in 1 blocks
==231996==   total heap usage: 14 allocs, 13 frees, 1,800 bytes allocated
==231996==
==231996== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==231996==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==231996==    by 0x48E3BA7: operator new(unsigned long) (in /usr/lib/llvm-10/lib/libc++.so.1.0)
==231996==    by 0x4028A8: tri::Area() const (in /home/ramcdona/Desktop/mwe)
==231996==    by 0x401E57: mesh::Area() const (in /home/ramcdona/Desktop/mwe)
==231996==    by 0x4017A9: main (in /home/ramcdona/Desktop/mwe)
==231996==
==231996== LEAK SUMMARY:
==231996==    definitely lost: 8 bytes in 1 blocks
==231996==    indirectly lost: 0 bytes in 0 blocks
==231996==      possibly lost: 0 bytes in 0 blocks
==231996==    still reachable: 0 bytes in 0 blocks
==231996==         suppressed: 0 bytes in 0 blocks
==231996==
==231996== For lists of detected and suppressed errors, rerun with: -s
==231996== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

使用 gcc 构建,Valgrind 的输出本质上是相同的。

最佳答案

正如 @Jarod42 所指出的,所写的赋值运算符是泄漏的根源。

缓存和可变都按预期工作。

更正后的代码应为:

    tri & operator=( const tri & t )
    {
        if ( this != &t )
        {
            m_P1 = t.m_P1;
            m_P2 = t.m_P2;
            m_P3 = t.m_P3;
            delete m_Area;
            if ( t.m_Area )
            {
                m_Area = new double( *(t.m_Area) );
            }
            else
            {
                m_Area = NULL;
            }
        }
        return *this;
    }

@TedLyngmo 建议的方法也可行。事实上,它可以完全避免此类问题。但是,我想了解为什么现有代码不起作用。

关于c++ - C++ 容器中的逻辑常量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70467583/

相关文章:

c++ - join() 是否释放了分配的内存? - C++11 线程

c++ - 最奇怪的 C++ 堆栈空()错误

python - 如何使用tcpdump和c++重新创建文件?

c++ - 调用成员函数集到特定变体的正确 C++ 变体语法是什么?

ruby - Ruby 中的字符串是可变的吗?

java - java中可变字符串和不可变字符串有什么区别

c++ - 为什么 C++ 允许我们在声明变量时将变量名括在括号中?

c++ - std::map\std::set 包含重复键

c++ - 如何制作可变深度的嵌套 map

c# - 如何修改我传入的对象集合的字段