返回指向函数静态数据的指针是不是合适?

Posted

技术标签:

【中文标题】返回指向函数静态数据的指针是不是合适?【英文标题】:Is it appropriate to return a pointer to static data of a function?返回指向函数静态数据的指针是否合适? 【发布时间】:2012-07-14 19:17:52 【问题描述】:

好吧,我需要返回一个指向将在函数内创建的类的实例的指针。这样合适吗?

这是示例代码:

template <typename T>
ImplicatedMembershipFunction<T>* 
TriangularMF<T>::minImplicate(const T &constantSet) const

    static ImplicatedType* resultingSet = new ImplicatedType();
    // do something to generate resultingSet...
    return resultingSet;

我想返回指针,因为容器中需要有基类的子类。在上面的代码中,ImplicatedType 是在TriangularMF&lt;T&gt; 中定义的一个类,并且是从ImplicatedMembershipFunction&lt;T&gt; 派生的。会有各种模板类,如TriangularMF,它们有一个从ImplicatedMembershipFunction&lt;T&gt; 派生的嵌套类,我需要以同样的方式处理它们。例如,在图书馆之外,我可能想做一些类似的事情:

TriangularMF<double> trmf(0,1,2);
TrapesoidalMF<double> trpmf(0,1,3,2); // a class like TriangularMF but
                                      // ImplicatedType is different 
ImplicatedMembershipFunction<double>* itrmf = trmf.implicate(0.6);
ImplicatedMembershipFunction<double>* itrpmf = trpmf.implicate(0.6); // same as above.

// use them in the same way:
vector<ImplicatedMembershipFunction<double>*> vec;
vec.push_back(itrmf); 
vec.push_back(itrpmf);

我不想使用移动语义或std::shared_ptr 等 C++11 功能的原因是我不想强迫我的队友在他们的计算机上安装更新版本的 g++。我不能给他们一个编译版本的库,因为它是大量模板化的。

编辑 该库将被线程化。特别是TriangularMF&lt;T&gt;::minImplicate 将同时在多个线程中运行。因此,将minImplicate 设为一个相互任务,对性能毫无意义。

【问题讨论】:

支持 C++11 的 GCC 版本可以追溯到很久以前。如果他们的版本不支持它,他们无论如何都应该升级。 我相信static ImplicatedType* resultingSet = new ImplicatedType(); 会导致内存泄漏,因为您每次调用都会分配一个新实例,可能会丢失最后一个实例。 @Chowlett 不会,每个专业只会调用一次。 安全合法,定义明确,并不完全是邪恶的;合适是什么意思? @sorush-r,也许这一切都是为了指出并解释优势。 This distribution 让它变得非常简单而且效果很好。 【参考方案1】:

返回指针本身不是问题,但您必须定义关于谁创建和谁销毁的干净“策略”。

在您的代码中,您定义了一个静态指针,在第一次遇到它的(指针)定义时使用 new 对象进行初始化。

指针本身会在main()返回后被销毁,但是它指向的对象呢? 如果您让其他东西来处理删除,即使对象不再存在,您的函数也会继续返回该指针。如果你让它在那里,它将在程序结束时被杀死(不是“危险的”泄漏,因为它只是一个对象,但如果它的析构函数必须采取一些明智的行动呢?)

您最有可能声明的不是静态指针,而是静态 OBJECT,然后返回......它的地址或引用。

通过这种方式,对象被授予在程序终止前存在并在main() 返回后被正确销毁。

template <typename T>
ImplicatedMembershipFunction<T>* 
TriangularMF<T>::minImplicate(const T &constantSet) const

    static ImplicatedType resultingSet(....);
    return &resultingSet;
 

请注意,我删除了您的“做某事...”,因为它每次都会执行(不仅仅是第一次)要初始化ImplicatedType,您最好依赖构造函数。 或者,如果您不能一次性构建它,请执行以下操作

template <typename T>
ImplicatedMembershipFunction<T>* 
TriangularMF<T>::minImplicate(const T &constantSet) const

    static ImplicatedType* resultingSet=0;
    static bool init=true;
    if(init)
    
        init=false; 
        static ImplicatedType result;
        resultingSet=&result;
        // do something to generate resultingSet...
    
    return resultingSet;

如果您处于多线程情况,您还需要一个静态互斥锁,在if(init) 之前锁定它,在返回时解锁。

【讨论】:

我可以为ImplicatedType 编写一个构造函数,它可以一次性完成。我担心多线程。如果我必须将代码包装在互斥锁/解锁中,线程将使我的代码比非线程代码慢得多。我应该为 ARM Cortex 2 核心处理器编写一个库,并且不想忽略线程的好处(如果可能的话) 无争用情况下的锁是原子增量和取不慢。很难设计出性能差异显着的情况。 @sorush-r:这是一个“错误的问题”:如果编译器遵循新标准,它会在初始化静态之前锁定。因此,如果您再次锁定内部,则不会添加“性能损失”。 (它只发生一次)如果编译器没有,你必须锁定,并获得与上述相同的性能。 @AndrewTomazos-Fathomling 这不是我提到的重点。该代码将为每个Rule&lt;T,N,X&gt; 生成一个单独的线程。 (这里的规则并不重要)对于一个简单的 FIS,会有 12 个线程。每个线程都必须暗示说 4 个成员函数。如果蕴涵是一个交互过程,上下文切换时间+蕴涵时间将超过所有规则线性蕴涵所需的时间。【参考方案2】:

这是单身人士常用的成语:

class CMyClass ;

CMyClass& MyClass() 
  static CMyClass mclass;
  return mclass;

CMyClass 将在第一次 MyClass() 函数调用时构造。

它看起来很像您的代码,除了指针异常,它会导致销毁此类 crated 实例时出现问题。如果你不想在这里使用 shared_ptr,那么考虑编写你自己的 shared_ptr 样模板,那么它应该可以正常工作。

[edit]如果这段代码要在多线程环境中使用,那么在这里使用智能指针会很棘手

【讨论】:

很多pthreads都会用到。我不明白为什么这个解决方案有问题! 在 C++11 之前,该语言不保证“如果尚未初始化则初始化”逻辑是线程安全的。 虽然它最近才成为标准的要求,但实际上本地静态变量初始化的自动锁定已经实施了很多很多年。所有常用的编译器(甚至是旧版本)都这样做。 ***.com/questions/9533649/… @AndrewTomazos-Fathomling:好点。很多时候,我们“保护”自己免受已经被我们保护的东西。【参考方案3】:

您可以使用这种技术,但返回一个引用。如果调用者需要一个指针来存储,调用者可以获取结果的地址。

template <typename T>
ImplicatedMembershipFunction<T> &
TriangularMF<T>::minImplicate(const T &constantSet) const

    static ImplicatedType* resultingSet = new ImplicatedType();
    // do something to generate resultingSet...
    return *resultingSet;

但是,代码的危险在于它本质上不是 MT 安全的。但是如果您知道minImplicate 中的代码是线程安全的,或者您的代码是单线程的,那么就没有问题。

【讨论】:

谁销毁了new 对象? @EmilioGaravaglia:在这种情况下,没有人。程序结束时会回收内存。 重点是:内存被回收,但对象析构函数没有被调用。假设它必须冲洗一些东西...... @EmilioGaravaglia:这只有在析构函数有事可做时才重要。为了说明这一点,我已经为您的帖子 +1 了。但是,如果析构函数没有做任何有用的事情,那么确保在关闭时调用析构函数实际上并不那么重要,而且如果静态对象实例相互依赖,则需要更多的代码来调试。

以上是关于返回指向函数静态数据的指针是不是合适?的主要内容,如果未能解决你的问题,请参考以下文章

用指向实例中方法的指针替换参数中的静态函数指针

如何从静态成员函数调用指向成员函数的指针?

指向任何类类型的非静态成员函数的 C++ 函数指针

成员函数指针和指向静态成员函数的指针

C++的多态总结(静态&动态)

指针函数不可以返回局部变量地址解决