Поддерживает ли C ++ отдельные универсальные методы, а не универсальные классы? - PullRequest
6 голосов
/ 09 апреля 2009

Важное замечание: Этот вопрос становится довольно длинным, если вы читаете его впервые, я предлагаю вам начать с нижней части, так как решение в подходе, но код немного вонючий.

Прочитав учебник по шаблонам , я смог изменить свой существующий класс для поддержки универсальных типов. Однако многие объекты уже зависят от этого, поэтому я ищу способ сделать метод универсальным, а не весь класс.

Я пробовал следующее, но похоже, что это поведение не поддерживается.

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

Джош, вот фактический исходный код (ну, я думаю, что это актуально, да ладно, дайте мне знать, если вы считаете, что я пропустил важный бит).

// 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, когда я не использую dynamic_cast ...

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 ]

11 голосов
/ 09 апреля 2009

Да, вы были довольно близко, попробуйте это:

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

Edit: Похоже, у вас возникли проблемы с компилятором, который не нашел определения для вашей шаблонной 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.
    }
};

Edit: Я не могу найти документацию по gcc, но вот документация корпорации Майкрософт , посвященная явной реализации шаблонов и библиотекам, дает некоторое представление о том, что происходит. Скорее всего, вы захотите либо включить реализацию в заголовок, как я предлагал ранее, либо вызвать GetRotatedCopy в библиотеке, либо явно создать ее экземпляр в библиотеке. См. ответ veefu ниже для синтаксиса.

Причина, по которой это работает не так, как в C #, заключается в том, что шаблоны в C ++, в отличие от C #, фактически создают совершенно новый класс / функцию для каждой отдельной комбинации параметров шаблона. например vector<int> - это совершенно другой класс (с другим набором скомпилированных методов), чем vector<string>. См. ответ Кевина для лучшего объяснения.

Что касается ошибки, которая возникает, когда вы не используете шаблон, это на самом деле не говорит вам много, поскольку до тех пор, пока вы на самом деле не создадите экземпляр шаблона, он не будет

RE Обновление 5,6,7

Ваш dynamic_cast не будет работать, вы можете использовать его, только если указатель действительно указывает на экземпляр класса, к которому вы приводите. (Он работает аналогично оператору 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.

4 голосов
/ 09 апреля 2009

В дополнение к тому, что сказал veefu, шаблоны в C ++ НЕ похожи на C # дженерики (тема помечена как C #, поэтому я предполагаю, что вы их несколько сравниваете) В C ++ реальный код генерируется не во время выполнения, а только во время компиляции, так что, если у вас есть класс с шаблоном в нем, вы должны либо иметь его в заголовочном файле, либо создавать EXACTLY, какие экземпляры он существует, иначе вы (обычно) получите ошибку компоновщика, потому что он не может найти то, что вы ищете, потому что он на самом деле никогда не создавался. При создании шаблонов компилятор фактически делает столько «копий» вашего шаблона, сколько вы создали экземпляров различных «видов» класса. Таким образом, из STL, если у вас есть Vector<int>, Vector<String> и Vector<char>, компилятор фактически выводит код для 3 различных классов. Вот почему шаблонные классы почти всегда определяются в заголовочных файлах, а не в скомпилированных библиотеках, потому что компилятор должен генерировать то, что вы используете.

Это отличается от универсальных в C # (и Java IIRC), где используются ссылки, и вы можете использовать только то, что вы указали универсальный для наследования, или от объекта. Вы должны объявить, что что-то реализует IComparable, чтобы использовать любой из методов из этого или любые другие ограничения, которые вы на него накладываете. По сути, когда вы используете универсальный, это трюк во время компиляции для обеспечения безопасности типов, но не на самом деле компилируется в классе. C ++ отличается тем, что если у вас есть класс с шаблонным полем, то в зависимости от размера этого класса результирующий класс будет больше или меньше, что влияет на фактический размер объекта.

Надеюсь, это имело какой-то смысл, хотя и было немного долго. На самом деле я не знал, что вы можете выполнять шаблонные функции, как показал mch.

3 голосов
/ 09 апреля 2009

Ошибка, которую вы получаете, может быть связана с другими проблемами в вашем примере кода. Вот пример шаблонного члена и свободных функций, который прекрасно компилируется с 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>();
1 голос
/ 10 апреля 2009

Может быть, это

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

находится в файле .cpp, кроме файла вызова? Если это так, это приведет к вашей ошибке. Реализация должна быть доступна в том же модуле компиляции (файл .cpp + все его включения), что и для вызова.

Таким образом, обычно реализации шаблонов помещаются в заголовочный файл, в котором находится объявление.

Су, ты должен проверить это.

1 голос
/ 09 апреля 2009

Обновления 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);

Я думаю, что это должно решить проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...