帮助我使这段代码异常安全

发布于 2024-07-14 03:52:10 字数 1566 浏览 10 评论 0原文

所以我有这个库代码,请参阅...

class Thing
{
public:

    class Obj 
    {
     public:
        static const int len = 16;

        explicit Obj(char *str)
        {
            strncpy(str_, str, len);
        }

        virtual void operator()() = 0;

    private:
        char str_[len];
    };

    explicit Thing(vector<Obj*> &objs) : objs_(objs) {}

    ~Thing() {
        for(vector<Obj*>::iterator i = objs_.begin(); i != objs_.end(); ++i) {
            delete *i;
        }
    }

private:
    vector<Obj*> objs_;
}

在我的客户端代码中...

   class FooObj : public Thing::Obj
    {
        virtual void operator()() {
            //do stuff
        }
    }

    class BarObj : public Thing::Obj
    {
        virtual void operator()() {
            //do different stuff
        }
    }

vector<Objs*> objs;
int nStructs = system_call(*structs);
for(int i = 0; i < nStructs; i++) {
    objs.push_back(newFooObj(structs[i].str));
}
objs.push_back(newBarObj("bar1");
objs.push_back(newBarObj("bar2");

Thing thing(objs);
// thing does stuff, including call Obj::()() on the elements of the objs_ vector

我所坚持的是异常安全。 就目前情况而言,如果任何 Obj 构造函数抛出异常,或者 Thing 构造函数抛出异常,则向量中已有的 Objs 将发生泄漏。 该向量需要包含指向 Objs 的指针,因为它们是多态使用的。 而且,我需要在这里处理任何异常,因为这是从不了解异常的旧代码库调用的。

在我看来,我的选择是:

  1. 将客户端代码包装在一个巨大的 try 块中,并清理 catch 块中的向量。
  2. 将 try 块放在所有分配周围,其中的 catch 块调用公共清理函数。
  3. 一些我还没有想到的基于 RAII 的聪明想法。
  4. 平底船。 实际上,如果构造函数抛出异常,应用程序无论如何都会陷入困境,但我想更优雅地处理这个问题。

So I have this library code, see...

class Thing
{
public:

    class Obj 
    {
     public:
        static const int len = 16;

        explicit Obj(char *str)
        {
            strncpy(str_, str, len);
        }

        virtual void operator()() = 0;

    private:
        char str_[len];
    };

    explicit Thing(vector<Obj*> &objs) : objs_(objs) {}

    ~Thing() {
        for(vector<Obj*>::iterator i = objs_.begin(); i != objs_.end(); ++i) {
            delete *i;
        }
    }

private:
    vector<Obj*> objs_;
}

And in my client code...

   class FooObj : public Thing::Obj
    {
        virtual void operator()() {
            //do stuff
        }
    }

    class BarObj : public Thing::Obj
    {
        virtual void operator()() {
            //do different stuff
        }
    }

vector<Objs*> objs;
int nStructs = system_call(*structs);
for(int i = 0; i < nStructs; i++) {
    objs.push_back(newFooObj(structs[i].str));
}
objs.push_back(newBarObj("bar1");
objs.push_back(newBarObj("bar2");

Thing thing(objs);
// thing does stuff, including call Obj::()() on the elements of the objs_ vector

The thing I'm stuck on is exception safety. As it stands, if any of the Obj constructors throw, or the Thing constructor throws, the Objs already in the vector will leak. The vector needs to contain pointers to Objs because they're being used polymorphically. And, I need to handle any exceptions here, because this is being invoked from an older codebase that is exception-unaware.

As I see it, my options are:

  1. Wrap the client code in a giant try block, and clean up the vector in the catch block.
  2. Put try blocks around all of the allocations, the catch blocks of which call a common cleanup function.
  3. Some clever RAII-based idea that I haven't thought of yet.
  4. Punt. Realistically, if the constructors throw, the application is about to go down in flames anyway, but I'd like to handle this more gracefully.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

回首观望 2024-07-21 03:52:10

由于您的 Thing 析构函数已经知道如何清理向量,因此您已经基本实现了 RAII< /a> 解决方案。 您可以使用空向量初始化 Thing,并添加一个成员函数以通过指针将新 Objs 添加到该向量,而不是创建 Objs 向量,然后将其传递给 Thing 的构造函数。

这样,如果 Obj 的构造函数抛出异常,编译器将自动调用 Thing 的析构函数,正确销毁已分配的任何 Obj。

Thing 的构造函数变为无操作:

explicit Thing() {}

添加一个 push_back 成员:

void push_back(Obj *new_obj) { objs_.push_back(new_obj); }

然后客户端中的分配代码变为:

Thing thing(objs);
int nStructs = system_call(*structs);
for(int i = 0; i < nStructs; i++) {
    thing.push_back(newFooObj(structs[i].str));
}
thing.push_back(newBarObj("bar1");
thing.push_back(newBarObj("bar2");

正如另一位发帖者所建议的,向量中的智能指针类型也可以很好地工作。 只是不要使用STL的auto_ptr; 它不遵循正常的复制语义,因此不适合在 STL 容器中使用。 Boost 和即将推出的 C++0x 提供的 shared_ptr 就可以了。

Since your Thing destructor already knows how to clean up the vector, you're most of the way towards a RAII solution. Instead of creating the vector of Objs, and then passing it to Thing's constructor, you could initialize Thing with an empty vector and add a member function to add new Objs, by pointer, to the vector.

This way, if an Obj's constructor throws, the compiler will automatically invoke Thing's destructor, properly destroying any Objs that were already allocated.

Thing's constructor becomes a no-op:

explicit Thing() {}

Add a push_back member:

void push_back(Obj *new_obj) { objs_.push_back(new_obj); }

Then the allocation code in your client becomes:

Thing thing(objs);
int nStructs = system_call(*structs);
for(int i = 0; i < nStructs; i++) {
    thing.push_back(newFooObj(structs[i].str));
}
thing.push_back(newBarObj("bar1");
thing.push_back(newBarObj("bar2");

As another poster suggested, a smart pointer type inside your vector would also work well. Just don't use STL's auto_ptr; it doesn't follow normal copy semantics and is therefore unsuitable for use in STL containers. The shared_ptr provided by Boost and the forthcoming C++0x would be fine.

挽你眉间 2024-07-21 03:52:10

答案 3 - 在向量中使用智能指针而不是 Obj*。 我建议 boost::shared_ptr

Answer 3 - Use smart pointers instead of Obj* in your vectors. I'd suggest boost::shared_ptr.

街道布景 2024-07-21 03:52:10

Obj 向量的性能可能非常差,因为向量可能必须在调整大小时移动对象,并且必须将它们全部复制。 指向对象的指针更好。

我使用了 Pointtainers 它将满足您的需求。 这是原始代码。

/*
 * pointainer - auto-cleaning container of pointers
 *
 * Example usage:
 * {
 *     pointainer< std::vector<int*> > v;
 *     // v can be manipulated like any std::vector<int*>.
 *
 *     v.push_back(new int(42));
 *     v.push_back(new int(17));
 *     // v now owns the allocated int-s
 *
 *     v.erase(v.begin());
 *     // frees the memory allocated for the int 42, and then removes the
 *     // first element of v.
 * }
 * // v's destructor is called, and it frees the memory allocated for
 * // the int 17.
 *
 * Notes:
 * 1. Assumes all elements are unique (you don't have two elements
 *    pointing to the same object, otherwise you might delete it twice).
 * 2. Not usable with pair associative containers (map and multimap).
 * 3. For ANSI-challenged compilers, you may want to #define
 *    NO_MEMBER_TEMPLATES.
 *
 * Written 10-Jan-1999 by Yonat Sharon <yonat@@ootips.org>
 * Last updated 07-Feb-1999
 *
 * Modified June 9, 2003 by Steve Fossen
 *  -- to fix g++ compiling problem with base class typenames
 */

#ifndef POINTAINER_H
#define POINTAINER_H

#ifdef NO_MEMBER_TEMPLATES
    #include <functional> // for binder2nd
#endif

template <typename Cnt>
class pointainer : public Cnt
{
public:
    // sf - change to fix g++ compiletime errors
#ifdef USE_USING_NOT_TYPEDEF
    // I get compile errors with this
    using typename Cnt::size_type;
    using typename Cnt::difference_type;
    using typename Cnt::reference;
    using typename Cnt::const_reference;
    using typename Cnt::value_type;
    using typename Cnt::iterator;
    using typename Cnt::const_iterator;
    using typename Cnt::reverse_iterator;
    using typename Cnt::const_reverse_iterator;
#else
    // this way works
    typedef typename Cnt::size_type                 size_type;
    typedef typename Cnt::difference_type           difference_type;
    typedef typename Cnt::reference                 reference;
    typedef typename Cnt::const_reference           const_reference;
    typedef typename Cnt::value_type                value_type;
    typedef typename Cnt::iterator                  iterator;
    typedef typename Cnt::const_iterator            const_iterator;
    typedef typename Cnt::reverse_iterator          reverse_iterator;
    typedef typename Cnt::const_reverse_iterator    const_reverse_iterator;
#endif

    typedef pointainer< Cnt > its_type;

    pointainer()                            {}
    pointainer( const Cnt &c )   : Cnt( c ) {}

    its_type &operator=( const Cnt &c )      { Cnt::operator=( c ); return *this;        }
    ~pointainer()                            { clean_all();                              }

    void clear()                             { clean_all();   Cnt::clear();              }
    iterator erase( iterator i )             { clean( i );    return Cnt::erase( i );    }
    iterator erase( iterator f, iterator l ) { clean( f, l ); return Cnt::erase( f, l ); }

    // for associative containers: erase() a value
    size_type erase( const value_type& v )
    {
        iterator i = find( v );
        size_type found( i != end() ); // can't have more than 1
        if( found )
            erase( i );
        return found;
    }

    // for sequence containers: pop_front(), pop_back(), resize() and assign()
    void pop_front()    { clean( begin() ); Cnt::pop_front();                 }
    void pop_back()     { iterator i( end() ); clean( --i ); Cnt::pop_back(); }

    void resize( size_type s, value_type c = value_type() )
    {
        if( s < size() )
            clean( begin() + s, end() );
        Cnt::resize( s, c );
    }

#ifndef NO_MEMBER_TEMPLATES
    template <class InIter> void assign(InIter f, InIter l)
#else
    void assign( iterator f, iterator l )
#endif
    {
        clean_all();
        Cnt::assign( f, l );
    }

#ifndef NO_MEMBER_TEMPLATES
    template <class Size, class T> void assign( Size n, const T& t = T() )
#else
    void assign( size_t n, const value_type& t = value_type() )
#endif
    {
        clean_all();
        Cnt::assign( n, t );
    }

    // for std::list: remove() and remove_if()
    void remove( const value_type& v )
    {
        clean( std::find( begin(), end(), v ));
        Cnt::remove( v );
    }

#ifndef NO_MEMBER_TEMPLATES
    template <class Pred>
#else
    typedef std::binder2nd< std::not_equal_to< value_type > > Pred;
#endif
    void remove_if(Pred pr)
    {
        for( iterator i = begin(); i != end(); ++i )
            if( pr( *i ))
                clean( i );

        Cnt::remove_if( pr );
    }

private:
    void clean( iterator i )                { delete *i; *i = 0;            } // sf add *i = NULL so double deletes don't fail
    void clean( iterator f, iterator l )    { while( f != l ) clean( f++ ); }
    void clean_all()                        { clean( begin(), end() );      }

    // we can't have two pointainers own the same objects:
    pointainer( const its_type& ) {}
    its_type& operator=( const its_type& ) { return  NULL;} // sf - return null to remove no return value warning..
};

#endif // POINTAINER_H

Vector of Obj can be very poor for performance, since a vector could have to move object on resize and has to copy them all. Pointers to objects are better.

I've used Pointainers which will do what you need. Here is the original code.

/*
 * pointainer - auto-cleaning container of pointers
 *
 * Example usage:
 * {
 *     pointainer< std::vector<int*> > v;
 *     // v can be manipulated like any std::vector<int*>.
 *
 *     v.push_back(new int(42));
 *     v.push_back(new int(17));
 *     // v now owns the allocated int-s
 *
 *     v.erase(v.begin());
 *     // frees the memory allocated for the int 42, and then removes the
 *     // first element of v.
 * }
 * // v's destructor is called, and it frees the memory allocated for
 * // the int 17.
 *
 * Notes:
 * 1. Assumes all elements are unique (you don't have two elements
 *    pointing to the same object, otherwise you might delete it twice).
 * 2. Not usable with pair associative containers (map and multimap).
 * 3. For ANSI-challenged compilers, you may want to #define
 *    NO_MEMBER_TEMPLATES.
 *
 * Written 10-Jan-1999 by Yonat Sharon <yonat@@ootips.org>
 * Last updated 07-Feb-1999
 *
 * Modified June 9, 2003 by Steve Fossen
 *  -- to fix g++ compiling problem with base class typenames
 */

#ifndef POINTAINER_H
#define POINTAINER_H

#ifdef NO_MEMBER_TEMPLATES
    #include <functional> // for binder2nd
#endif

template <typename Cnt>
class pointainer : public Cnt
{
public:
    // sf - change to fix g++ compiletime errors
#ifdef USE_USING_NOT_TYPEDEF
    // I get compile errors with this
    using typename Cnt::size_type;
    using typename Cnt::difference_type;
    using typename Cnt::reference;
    using typename Cnt::const_reference;
    using typename Cnt::value_type;
    using typename Cnt::iterator;
    using typename Cnt::const_iterator;
    using typename Cnt::reverse_iterator;
    using typename Cnt::const_reverse_iterator;
#else
    // this way works
    typedef typename Cnt::size_type                 size_type;
    typedef typename Cnt::difference_type           difference_type;
    typedef typename Cnt::reference                 reference;
    typedef typename Cnt::const_reference           const_reference;
    typedef typename Cnt::value_type                value_type;
    typedef typename Cnt::iterator                  iterator;
    typedef typename Cnt::const_iterator            const_iterator;
    typedef typename Cnt::reverse_iterator          reverse_iterator;
    typedef typename Cnt::const_reverse_iterator    const_reverse_iterator;
#endif

    typedef pointainer< Cnt > its_type;

    pointainer()                            {}
    pointainer( const Cnt &c )   : Cnt( c ) {}

    its_type &operator=( const Cnt &c )      { Cnt::operator=( c ); return *this;        }
    ~pointainer()                            { clean_all();                              }

    void clear()                             { clean_all();   Cnt::clear();              }
    iterator erase( iterator i )             { clean( i );    return Cnt::erase( i );    }
    iterator erase( iterator f, iterator l ) { clean( f, l ); return Cnt::erase( f, l ); }

    // for associative containers: erase() a value
    size_type erase( const value_type& v )
    {
        iterator i = find( v );
        size_type found( i != end() ); // can't have more than 1
        if( found )
            erase( i );
        return found;
    }

    // for sequence containers: pop_front(), pop_back(), resize() and assign()
    void pop_front()    { clean( begin() ); Cnt::pop_front();                 }
    void pop_back()     { iterator i( end() ); clean( --i ); Cnt::pop_back(); }

    void resize( size_type s, value_type c = value_type() )
    {
        if( s < size() )
            clean( begin() + s, end() );
        Cnt::resize( s, c );
    }

#ifndef NO_MEMBER_TEMPLATES
    template <class InIter> void assign(InIter f, InIter l)
#else
    void assign( iterator f, iterator l )
#endif
    {
        clean_all();
        Cnt::assign( f, l );
    }

#ifndef NO_MEMBER_TEMPLATES
    template <class Size, class T> void assign( Size n, const T& t = T() )
#else
    void assign( size_t n, const value_type& t = value_type() )
#endif
    {
        clean_all();
        Cnt::assign( n, t );
    }

    // for std::list: remove() and remove_if()
    void remove( const value_type& v )
    {
        clean( std::find( begin(), end(), v ));
        Cnt::remove( v );
    }

#ifndef NO_MEMBER_TEMPLATES
    template <class Pred>
#else
    typedef std::binder2nd< std::not_equal_to< value_type > > Pred;
#endif
    void remove_if(Pred pr)
    {
        for( iterator i = begin(); i != end(); ++i )
            if( pr( *i ))
                clean( i );

        Cnt::remove_if( pr );
    }

private:
    void clean( iterator i )                { delete *i; *i = 0;            } // sf add *i = NULL so double deletes don't fail
    void clean( iterator f, iterator l )    { while( f != l ) clean( f++ ); }
    void clean_all()                        { clean( begin(), end() );      }

    // we can't have two pointainers own the same objects:
    pointainer( const its_type& ) {}
    its_type& operator=( const its_type& ) { return  NULL;} // sf - return null to remove no return value warning..
};

#endif // POINTAINER_H
把昨日还给我 2024-07-21 03:52:10

您始终可以使用 Obj 向量,而不是指向 Obj 的指针向量。 在这种情况下,您需要确保可以安全地复制 Obj(当它包含指针时很危险)。 不过,由于您的 Obj 仅包含固定大小的 char 数组,因此它应该是安全的。

Instead of a vector of pointers to Obj you always can use a vector of Obj. In that case you need to make sure you can copy Obj safely (dangerous when it contains pointers). As your Obj contains only a fixed size char array it should be safe, though.

和影子一齐双人舞 2024-07-21 03:52:10

在不将 objs 中存储的类型更改为可以复制并管理 Obj* 本身的类型的情况下,您需要添加一个 try/catch 块来在抛出异常时清理 objs。 最简单的方法是这样做:

vector<Obj *> objs;

try {...}
catch (...) 
{
    // delete all objs here
    throw;
}

尽管如果没有抛出异常,您无论如何都会想要清理对象。

Without changing the type stored in objs to a type that can be copied and manage Obj* itself, you need to add a try/catch block to cleanup objs if an exception is thrown. The easiest way would be to do this:

vector<Obj *> objs;

try {...}
catch (...) 
{
    // delete all objs here
    throw;
}

Although you'll want to clean up objs anyway if an exception isn't thrown also.

豆芽 2024-07-21 03:52:10

嗯。 我真的很喜欢 Commodore Jaeger 的想法; 它巧妙地消除了这段代码给我带来的一些奇怪的味道。 我现在不愿意引入 Boost 库; 这是一个有点保守的代码库,我宁愿把它带入 21 世纪,蠕动和抱怨,而不是踢腿和尖叫。

Hmmm. I really like Commodore Jaeger's idea; it neatly clears up some of the funny smells this code was giving me. I'm reluctant to bring in the Boost libraries at this point; this is a somewhat conservative codebase, and I'd rather bring it into the 21st century squirming and complaining than kicking and screaming.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文