代码之家  ›  专栏  ›  技术社区  ›  davenpcj

C++支持单独的泛型方法而不是泛型类吗?

  •  6
  • davenpcj  · 技术社区  · 6 年前

    重要: 这个问题越来越长了,如果这是你第一次读这个,我建议你从底部开始,因为解决方案是有一个全面的方式,但代码有点臭。

    读完一篇 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>();
    

    这应该不起作用吗,或者这是我正在困惑的其他地方的一个bug?

    未定义的引用 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:

    乔希,这是实际的源代码(好吧,我认为是相关的,安威-如果你认为我跳过了一个重要的部分,请告诉我)。

    // 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:

    参考更新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 回复  |  直到 6 年前
        1
  •  11
  •   Community CDub    7 年前

    是的,你很接近,试试这个:

    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 为了更好的解释。

    至于不使用模板时的错误,实际上并没有告诉您多少,因为在实际实例化模板之前,它不会

    重新更新5、6、7

    您的动态类型转换将不起作用,只有当指针实际指向要转换的类的实例时,才能使用它。(它的工作原理与 as c中的运算符。

    我现在怀疑,你想要的是 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。

        2
  •  4
  •   Salman A    15 年前

    加上VEEFU所说的,C++中的模板不像C类泛型(主题被标记为C,所以我假设你有点比较)。在C++中,实际代码不是在运行时生成的,而是在编译时生成的,因此,如果您有一个带有模板的类,则必须在头文件中拥有它,或者必须实例化它存在的实例,否则您将(通常)得到链接错误,因为它找不到您要找的,因为它从来没有运行过。盟军制造。在做模板时,编译器实际上会制作模板类的“副本”,就像你实例化了不同类型的类一样。所以从STL,如果你有 Vector<int> , Vector<String> Vector<char> 编译器实际上为3个不同的类输出代码。这就是为什么模板化类几乎总是在头文件中定义,而不是在编译的库中定义,因为编译器需要生成您正在使用的内容。

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

    我希望这有点道理,尽管有点长。我真的不知道你能像MCH所显示的那样做模板化的函数。

        3
  •  3
  •   mch    15 年前

    您得到的错误可能是由于示例代码中的其他问题造成的。下面是一个使用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>();
    
        4
  •  1
  •   veefu    15 年前

    我认为,更新3和4会泄露,但如果不知道你的项目布局,就很难分辨出来。

    如果您试图公开来自库的模板以便从其他位置调用,则必须

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

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

    我认为这应该解决问题。

        5
  •  1
  •   mmmmmmmm    15 年前

    可能是那个

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

    是否在.cpp文件中而不是调用文件中? 如果是这样,这将导致您的错误。实现必须与调用在同一编译单元(.cpp文件+其所有include)中可用。

    所以通常模板实现放在声明所在的头文件中。

    所以你应该检查一下这个。