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

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;
}

我希望返回指针,因为需要在容器中有一个基类的子类。在上面的代码中,ImplicatedType是在TriangularMF<T>中定义的一个类,并且派生自ImplicatedMembershipFunction<T>。将会有各种模板类,如TriangularMF,它们都有一个从ImplicatedMembershipFunction<T>派生的嵌套类,我需要用同样的方式处理它们。例如,在库外,我可能想要做这样的事情:

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);

我不想使用C++11的移动语义或std::shared_ptr等功能,原因是我不想强制我的队友在他们的计算机上安装更新的g++版本。因为库中有大量模板代码,所以我无法给他们提供已编译的库版本。 编辑 这个库将被线程化。特别是,TriangularMF<T>::minImplicate将同时在多个线程中运行。因此,将minImplicate作为互斥任务对性能没有意义。

3
支持C++11的GCC版本相当久远。如果他们的版本不支持它,他们应该升级。 - chris
1
@Chowlett 不会的,它只会在每个专业领域被调用一次。 - Luchian Grigore
1
它是安全、合法、定义明确且并非完全邪恶的;你所说的“适当”是指什么? - Alan Stokes
1
@sorush-r,也许关键在于指出并解释其优点。这个发行版非常易用且效果很好。 - chris
@Chowlett:不是的:new 调用用于初始化静态变量。它仅在创建静态指针 istefs 时发生。尽管有 =,但该语句不是赋值语句。问题是...谁来销毁它? - Emilio Garavaglia
显示剩余5条评论
3个回答

3

这是一个常用的单例模式:

class CMyClass {};

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

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

除了指针可能会导致销毁这个创建的实例时出现问题外,它看起来与您的代码非常相似。如果您不想在这里使用shared_ptr,请考虑编写自己的类似shared_ptr的模板,那么它应该可以正常工作。

[编辑] 如果此代码将在多线程环境中使用,则在此处使用智能指针将会很棘手。


它将在许多pthread中使用。我看不出为什么这个解决方案有问题! - sorush-r
3
在C++11之前,该语言不能保证“如果尚未初始化则进行初始化”的逻辑是线程安全的。 - Alan Stokes
2
虽然自动锁定局部静态变量初始化只是最近才成为标准要求,但实际上该功能已经实现了很多年。所有常用的编译器(即使是旧版本)都可以做到。 - Andrew Tomazos
1
https://dev59.com/_WHVa4cB1Zd3GeqPjRaT - Andrew Tomazos
@AndrewTomazos-Fathomling:说得好。很多时候我们会“防御”一些我们已经被保护的东西。 - Emilio Garavaglia

3

返回指针本身并不是问题,但你必须定义一个清晰的“策略”,关于谁创建和销毁。

在你的代码中,你定义了一个静态指针,它在第一次遇到它(指针)的定义时被初始化为一个新对象。

指针本身将在main()返回后立即被销毁,但是它所指向的对象呢? 如果你让其他东西来处理删除,即使对象不存在,你的函数仍然会继续返回该指针。如果你让它存在那里,它将在程序结束时被清除(不是一个“危险”的泄漏,因为它只是一个对象,但如果它的析构函数需要采取一些敏感的操作呢?)

你最好声明一个静态对象,而不是静态指针,并返回...它的地址或引用。

这样,该对象将保证存在直到程序终止,并在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
在无竞争的情况下,锁定是原子递增和获取,速度并不慢。很难构造出性能差异显著的情况。 - Andrew Tomazos
@sorush-r:这是一个“虚假问题”:如果编译器遵循新标准,在初始化静态变量之前会进行锁定。因此,如果在内部再次锁定,不会增加“性能惩罚”(仅发生一次)。如果编译器没有遵循新标准,则必须进行锁定,并获得与上述相同的性能。 - Emilio Garavaglia
@AndrewTomazos-Fathomling,那不是我提到的重点。该代码将为每个Rule<T,N,X>生成一个单独的线程。(这里不重要)对于简单的FIS,将有大约12个线程。每个线程必须实现4个成员函数。如果蕴含是一个相互过程,上下文切换时间+蕴含时间将超过线性蕴含所有规则所需的时间。 - sorush-r

1

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

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对象? - Emilio Garavaglia
@EmilioGaravaglia:在这种情况下,没有人。当程序结束时,内存会被回收。 - jxh
这就是问题所在:内存被回收了,但对象的析构函数没有被调用。假设它需要清除一些东西... - Emilio Garavaglia
@EmilioGaravaglia:只有当析构函数有实际作用时才显得重要。我已经为你的观点点赞了。但是,如果析构函数没有做任何有用的事情,确保在关闭时调用它们并不是非常重要的,而且如果静态对象实例相互依赖,这只会增加代码调试的难度。 - jxh

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