C ++是否支持单个泛型方法而不是泛型类?

时间:2009-04-09 17:35:40

标签: c# c++ templates

  

重要提示:这个问题已经很长了,如果这是你第一次阅读这篇文章我建议你从底部开始,因为解决方案是在一个回合的路上,但是代码有点臭。

阅读tutorial on templates后,我能够更改现有的类以支持泛型类型。但是,许多对象已经依赖于此,所以我正在寻找一种方法来制作方法而不是整个类。

我尝试过以下操作,但看起来不支持此行为。

// foobar1.h
// Don't want the entire class to be generic.
//template<class T>
class FooBar1
{
public:
    template<class T> T Foo();
}

// foobar2.h
class FooBar2 : public FooBar1
{
}

// foobar1.cpp
template<class T>
T FooBar1::Foo()
{
    return something;
}

// test.cpp
FooBar1 fb1;
FooBar2 fb2 = fb1.Foo<FooBar2>();

这应该不起作用,还是其他地方的错误,我感到困惑?

  

FooBar2 Foo<FooBar2>()

的未定义引用

为了实现我想要达到的目标的某种观点,以下是我在C#中的表现......

public class FooBar1
{
    public T Foo<T>()
        where T : FooBar1
    {
        return something;
    }
}

public class FooBar2 : FooBar1 { }

FooBar1 fb1 = new FooBar1();
FooBar2 fb2 = fb1.Foo<FooBar2>();

我有什么方法可以做类似于C ++的事情吗?

更新1:

刚刚纠正了一些小的语法细节(我的意思是让Foo公开,然后返回T,而不是FooBar2)。仍然得到编译器错误...当我删除模板行为时,错误消失了,到目前为止答案说我正在做的是有效的...但如果是,那么为什么我仍然得到错误?谢谢你的回答!

更新2:

Josh,这是实际的源代码(好吧,我认为是相关的,anwyay - 如果你认为我已经跳过了重要的一点,请告诉我。)

// ImageMatrix.h
class ImageMatrix : public VImage
{
public:
    // ... various functions ...
    template<class T> T GetRotatedCopy(VDouble angle);
}

// ImageFilter.h
class ImageFilter : public ImageMatrix
{
    // ... various functions ...
}

// ImageMatrix.cpp
template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    // ... create a new instance of ImageMatrix and return it.
}

// ImageProcessor.cpp
ImageFilter filter2 = filterPrototype.GetRotatedCopy<ImageFilter>(90);

这是实际的编译器错误:

  

/home/nick/Projects/ViMRID/vimrid/Debug/libvimrid.so:未定义引用`vimrid :: imaging :: processing :: ImageFilter vimrid :: imaging :: ImageMatrix :: GetRotatedCopy(double)'

更新3:

顺便说一句,除了实现行之外的所有东西都位于库中;所以它是从一个单独的二进制文件调用的......这有关系吗?更正;它都在同一个图书馆里。但是所有块都是不同的文件。

更新4:

当我注释掉实现行(ImageFilter filter2 = filterPrototype ...)时,它构建得很好,所以看起来就是这一行导致它......

更新5(已解决?):

还有问题......这可能是命名空间的问题吗?抓一点,好吧,我现在已经掌握了模板的概念! :)模板定义必须与声明一起在标题中(对吗?) - 所以现在我已将声明移到ImageMatrix.h,所有内容都会编译。但是,我必须使用dynamic_cast来使其正常工作;这是正确的吗?如果我要离开,请纠正我!

// This is in the header file!
// Help!!! This looks really really smelly...
template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    ImageMatrix image = _getRotatedCopy(angle);
    ImageMatrix *imagePtr = &image;
    return *dynamic_cast<T*>(imagePtr);
}

更新6:

当我不使用dynamic_cast时,请参阅更新5 ...

template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    ImageMatrix image = _getRotatedCopy(angle);
    ImageMatrix *imagePtr = &image;
    //return *dynamic_cast<T*>(imagePtr);
    return *imagePtr;
}

......我收到了这个错误...

../src/imaging/processing/../ImageMatrix.h: In member function ‘T vimrid::imaging::ImageMatrix::GetRotatedCopy(vimrid::VDouble) [with T = vimrid::imaging::processing::ImageFilter]’:
../src/imaging/processing/ImageProcessor.cpp:32:   instantiated from here
../src/imaging/processing/../ImageMatrix.h:45: error: conversion from ‘vimrid::imaging::ImageMatrix’ to non-scalar type ‘vimrid::imaging::processing::ImageFilter’ requested
make: *** [src/imaging/processing/ImageProcessor.o] Error 1

更新7:

另外,如果我在更新6中没有使用所有那些臭臭的代码......

class ImageMatrix : public VImage
{
public:
    template<class T> T GetRotatedCopy(VDouble angle);
private:
    ImageMatrix _getRotatedCopy(VDouble angle);
};

template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
    return _getRotatedCopy(angle);
}

...我得到的错误与更新6相同。

5 个答案:

答案 0 :(得分:11)

是的,你很亲密,试试这个:

class FooBar1
{
public:
    template<class T> T Foo();
};

class FooBar2 : public FooBar1
{
};

template<class T>
T FooBar1::Foo()
{
    return T();
}

int main()
{
   FooBar1 fb1;
   FooBar2 fb2 = fb1.Foo<FooBar2>();
}

您遇到的问题是您将FooBar1::Foo()的返回类型指定为FooBar2,您应该只将其设为T

如果你想为FooBar2做特定的事情,你可以专注于FooBar2:

template<>
FooBar2 FooBar1::Foo<FooBar2>()
{
    return FooBar2();
}

编辑: 听起来你在编译器没有找到模板化GetRotatedCopy的定义时遇到问题。 C ++中的模板相当挑剔,通常的做法是将整个模板实现放在头文件中。你可以试试这个:

class ImageMatrix : public VImage
{
public:
    // ... various functions ...
    template<class T> T GetRotatedCopy(VDouble angle)
    {
       // ... create a new instance of ImageMatrix and return it.
    }
};

编辑: 我找不到gcc文档,但这里有关于显式模板实例化和库的microsoft's documentation,它给出了一些关于发生了什么的想法。您可能希望按照我之前的建议在头中包含实现,或者在库中调用GetRotatedCopy,或者在库中显式实例化它。有关语法,请参阅下面的veefu's answer

与C#不同的原因是,与C#不同,C ++中的模板实际上为每个不同的模板参数组合创建了一个全新的类/函数。例如vector<int>是一个完全不同的类(使用不同的编译方法集),而不是vector<string>。有关更好的解释,请参阅Kevin's answer

当你不使用模板时,错误会消失,这实际上并没有告诉你多少,因为直到你实际实例化一个模板,它才不会

RE更新5,6,7

您的dynamic_cast不起作用,只有当指针实际指向您要转换的类的实例时,才能使用它。 (它的工作方式类似于C#中的as运算符)。

我现在怀疑,你想要的是CRTP。您从ImageFilter的实例开始,并希望在其上使用基类方法,并获取ImageFilter的新副本。尝试以下几点:

template <class T>
class ImageMatrix
{
public:
    T GetRotatedMatrix()
    {
        return T();
    }
};

class ImageFilter : public ImageMatrix<ImageFilter>
{
};

int main()
{
    ImageFilter filterPrototype;
    ImageFilter otherFilter = filterPrototype.GetRotatedMatrix();
}

否则,如果您确实想要从ImageMatrix开始并将其转换为ImageFilter,则必须在ImageFilter上添加一个构造函数,该构造函数将采用ImageMatrix。

答案 1 :(得分:4)

除了v​​eefu所说的,C ++中的模板不像C#泛型(主题被标记为C#,所以我假设你在某种程度上比较它们)。在C ++中,实际代码不是在运行时生成的,而是仅在编译时生成的,因此如果您有一个包含模板的类,您必须将它放在头文件中,或者您必须实例化实例化哪些实例它存在,否则你(通常)会得到一个链接器错误,因为它无法找到你正在寻找的东西,因为它实际上从未实现过。在进行模板时,编译器实际上会创建模板类的“副本”,因为您已经实例化了类的不同“种类”。因此,从STL开始,如果您有Vector<int>Vector<String>Vector<char>,编译器实际上会输出3个不同类的代码。这就是为什么模板化的类几乎总是在头文件中而不是在编译库中定义的原因,因为编译器需要生成您正在使用的内容。

这与使用引用的C#(和Java IIRC)中的Generics形成对比,并且您只能使用您指定的通用来继承或从对象继承。您需要声明某些东西实现IComparable以使用其中的任何方法,或者您对其施加的任何其他限制。基本上,当你使用泛型时,它是一个编译时技巧,以确保类型安全,但不是在课堂上实际编译。 C ++是不同的,因为如果你有一个带有模板化字段的类,那么根据该类的大小,结果类会更大或更小,这会影响对象的实际大小。

我希望这有点道理,虽然它有点长。我实际上并不知道你可以像mch那样做模板化的功能。

答案 2 :(得分:3)

您获得的错误可能是由于示例代码中的其他问题所致。下面是一个模板成员和自由函数的例子,可以用G ++编译好。

// Don't want the entire class to be generic.
//template<class T>
class FooBar1
{
public:
    template<class T> T Foo();
};

class FooBar2 : public FooBar1
{
};

template<class T>
T FooBar1::Foo()
{
    return T();
}

template <class T>
T FreeFunction()
{
    return T();
}

int main()
{
    FooBar1 fb1;
    FooBar2 fb2 = fb1.Foo<FooBar2>();

    FooBar2 fb3 = FreeFunction<FooBar2>();
}

在C ++中,通常在头文件中包含模板化类的声明和定义。这样做可确保编译器为模板化函数或类生成代码,并在编译时填充特定模板参数。

可以将声明放在头文件中,将定义放在cpp文件中。如果您有少量已模仿的已知类型,则此方法可以正常工作。有必要使用您希望与之一起使用的类型在cpp文件中显式实例化类或函数,以便编译器可以正确生成代码。

标题文件:

template <class T>
T DoSomething();

Cpp文件:

template <class T>
T DoSomething();
{
    return T();
}

template FooBar1 DoSomething<FooBar1>();
template FooBar2 DoSomething<FooBar2>();

答案 3 :(得分:1)

我认为,更新3和4会将其丢弃,但在不知道项目布局的情况下很难判断。

如果您尝试从要从其他位置调用的库中公开模板,则必须

答:在库的头文件中包含模板定义

B:显式实例化库中的模板代码,以便以后的代码有一个实现链接到

template<class T>
T ImageMatrix::GetRotatedCopy(VDouble angle)
{
  // ... create a new instance of ImageMatrix and return it.
}
// Add the following line
template ImageFilter ImageMatrix::GetRotatedCopy<ImageFilter>(VDouble);

我认为应该解决问题。

答案 4 :(得分:1)

可能是那个

template<class T>
T FooBar1::Foo()
{
    return something;
}

是否在调用文件以外的.cpp文件中? 如果是这样会导致您的错误。该实现必须在同一个编译单元(.cpp文件及其所有包含的内容)中作为调用提供。

因此,通常将模板实现放在声明所在的头文件中。

你应该检查一下。