C++删除指针的函数对象似乎有效

3

以下是一个在向量中删除指针的函数对象的建议

struct DeleteFromVector
{
    template <class T>
    void operator() ( T* ptr) const
    {
            delete ptr;
    }
};

使用时需调用

std::for_each(aVec.begin(), aVec.end(), DeleteFromVector());

我曾经写过类似的东西(虽然最初没有结构体内模板的巧妙移动):

    struct DeleteObj 
    {
        template<typename ObjType>
        inline void operator() (ObjType obj)
        { 
            delete obj; 
            obj = NULL; 
        };
    };

在调试器中逐步执行似乎是有效的,即obj被识别为指针,没有崩溃。

我有点困惑为什么两者都起作用,并且也想听听对使用const和inline的意见。

谢谢, 罗伯特


4
请注意,obj = NULL 只影响局部参数,在参数上没有任何影响。此外,在删除指针后将其设置为空指针被认为是不良风格,因为它倾向于隐藏设计问题而不是解决它们。 - fredoverflow
另外,请查看:http://www.codeproject.com/KB/stl/freeptrfnc.aspx - Nemanja Trifunovic
6个回答

3
第一种方法更可取,因为只有当向量元素类型为指针时才会进行编译。如果将向量元素类型转换为非空指针,则第二种方法将进行编译,这可能会导致灾难性后果。
Functors通常被声明为const,虽然不经常发生区别,但也不会造成任何代价。

你不能删除一个非指针。错误出现的原因在于不同的获取位置。 - UncleBens
@UncleBens:即使非指针类型可以转换为指针类型?你是说delete是一个例外吗? - john

2
这个方案的实现原理是 ObjType 推断为 WhateverTypeYouHaveInYourVector *。而第一种解决方法中,T 推断为 WhateverTypeYouHaveInYourVector。后者更好,因为它要求参数是指针,而在你的解决方案中,参数可以是任何类型,甚至是不可删除(delete)的类型。
另外,obj = NULL 是无意义的,因为你使用的是按值传递的参数。因此,将被设置为空(null)的指针是函数内部的本地拷贝,而不是包含在向量(vector)中的指针。你可以改用引用指针的方式:
struct Deleter
{
    template <typename T>
    void operator()( T * & ptr) const
    {
        delete ptr;
        ptr = 0;
    }
};

即使有一个引用,你只会使该参数无效,而不是指针的任何其他副本。 - Matthieu M.
@Matthieu M.:显然是的。如果被删除的对象被应用程序中许多指针所指向,最好的做法是开始使用一些智能指针,如shared_ptr/weak_ptr - Luc Touraille
回复:ptr = NULL 和按值传递 <深叹> - Robert
我想我正在努力理解它在没有明确声明类型时推导类型的魔力。自从我上次使用模板以来已经过了很长时间,这是我第一次涉足函数对象。 - Robert

2
  • 为什么两个都有效:如果调用 operator()(U*),第一个匹配 T = U,第二个匹配 ObjType = U*

  • const:这只是表示您没有修改任何成员变量,但由于结构体为空,所以没有任何区别。一些算法可能将它们的函数对象作为常量,因此仅允许访问常量成员函数(但不在 for_each 中)。无论如何,由于没有状态,您的成员函数可能也是静态的。

  • 内联只是编译器的提示;很有可能构造函数会被内联,不管怎样。类定义内的成员函数定义已经隐式地是内联的。


2

为什么两个模板都能正常工作?

在第一个模板中,您将Obj*传递给接受T*的模板方法,因此编译器推断出TObj

在第二个模板中,您将Obj*传递给接受T的模板方法,因此编译器推断出TObj*


使用inline

inline是多余的,因为在类定义中定义的成员函数隐式为inline


使用const

如果可能,函数对象应该将其operator()声明为const。这是因为通过非const引用传递临时对象是非法的。调用std::for_each(...,..., Functor());可能不会编译,除非Functor具有一个operator(T)const

由于Microsoft编译器实现了一种非标准扩展,允许通过非const引用传递临时对象,并默认启用此非标准行为,因此这个问题变得混乱不清。


1
关于您对const的评论:大多数情况下(至少在标准库算法中),函数对象是按值传递的,因此使用const并不那么重要。此外,函数对象的一个优点是它们可以在调用之间保持状态,因此禁止它们修改此状态有点愚蠢。但是,如果operator()不修改状态,则没有不将其设置为const的意义(越const越好)。 - Luc Touraille
1
@Luc Touraille:虽然在许多情况下维护状态确实有效,但它并未得到标准的认可。函数对象被复制的次数是不确定的,这意味着任何依赖于先前操作留下的状态的操作可能无法按预期工作。总之:避免在函数对象中维护状态(您可以在函数对象中维护指向外部维护状态的指针或引用,这是完全可以的)。 - David Rodríguez - dribeas
如果我没记错的话(也有可能记错了),问题在于标准没有规定一个函数对象被复制的次数,这意味着一个函数对象必须是可复制的,并且如果作为临时对象传递,必须具有const operator()以与所有符合标准库实现的兼容。 - JoeG
@Luc Touraille:这是一般性评论。标准没有规定在任何算法中可以执行多少个函数对象的副本。我无法想象在for_each中进行复制的原因,但其他一些算法确实需要。例如,remove_if是通过find_if和额外的循环来实现的,谓词被传递给remove_if,然后复制到find_if中,并在find_if完成时丢弃该副本。这意味着在查找第一个元素时存储在谓词中的任何状态都将丢失。这是一个不同的例子,但标准的要求对两者都是相同的。 - David Rodríguez - dribeas
事实上,C++0x标准在§25.1/10中明确规定:除非另有规定,以函数对象为参数的算法允许自由复制这些函数对象。对于那些重视对象身份的程序员,应考虑使用指向非复制实现对象的包装类,例如reference_wrapper<T>(20.8.3),或其他等效解决方案。 - David Rodríguez - dribeas
显示剩余5条评论

1

两种方法都可以工作,因为它们都正确推导了模板参数,只是在一种情况下它是指针,在另一种情况下它是被指向的类型,因为签名已经包含了*

inline在这里是多余的,因为函数已经是内联的。此外,设置obj = NULL是无用的,因为它只将指针的本地副本设置为0,而不是--可能意图的--向量中的条目。


0
第二个示例可能无法正常工作,因为您没有将其转换为指针:
inline void operator() (ObjType* obj)

这应该可以工作,就像上面的例子一样。

inline是编译器优化的建议,它会将函数完全复制到每个调用它的地方。

const关键字有许多用途,取决于您在何处使用它。在您的情况下(在第一个示例中),它意味着调用该方法的类或对象不应更改,并且可以在const上下文中使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接