Есть ли какой-либо вариант использования для функции внутри класса после введения лямбда? - PullRequest
5 голосов
/ 29 июля 2011

Из статьи Википедии о Лямбда-функциях и выражениях :

пользователи часто хотят определять функции предикатов рядом с местом где они делают вызов функции алгоритма. В языке только один Механизм для этого: способность определять класс внутри функция. ... классы, определенные в функциях, не позволяют использовать их в шаблонах

Означает ли это, что использование вложенной структуры внутри функции безоговорочно считается устаревшим после установки C ++ 0x lambda?

Кроме того, что означает последняя строка в предыдущем абзаце? Я знаю, что вложенные классы не могут быть template; но эта строка не значит это.

Ответы [ 5 ]

8 голосов
/ 29 июля 2011

Я не уверен, что понимаю вашу путаницу, но я просто изложу все факты и позволю вам разобраться.:)

В C ++ 03 это было законно:

#include <iostream>

int main()
{
    struct func
    {
        void operator()(int x) const
        {
            std::cout << x << std::endl;
        }
    };

    func f; // okay
    f(-1); // okay

    for (std::size_t i = 0; i < 10; ++i)
        f(i) ; // okay
}

Но если бы мы попытались это сделать, это не было бы:

template <typename Func>
void exec(Func f)
{
    f(1337);
}

int main()
{
    // ...

    exec(func); // not okay, local classes not usable as template argument
}

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

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

C ++ 0x изменяет правила, чтобы позволить вышеуказанному коду работать.Они дополнительно добавили lambdas: синтаксис для создания объектов функций в виде выражений, например:

int main()
{
    // same function as above, more succinct
    auto func = [](int x){ std::cout << x << std::endl; };

    // ...
}

Это точно так же, как описано выше, но проще.Так есть ли у нас еще какое-нибудь применение для «настоящих» локальных классов?Конечно.В конце концов, у Lambda нет полной функциональности:

#include <iostream>

template <typename Func>
void exec(Func func)
{
    func(1337);
}

int main()
{
    struct func
    {
        // note: not possible in C++0x lambdas
        void operator()(const char* str) const
        {
            std::cout << str << std::endl;
        }

        void operator()(int val) const
        {
            std::cout << val << std::endl;
        }
    };

    func f; // okay
    f("a string, ints next"); // okay

    for (std::size_t i = 0; i < 10; ++i)
        f(i) ; // okay

    exec(f); // okay
}

Тем не менее, с лямбдами вы, вероятно, не увидите локальные классы больше, чем раньше, но по совершенно другим причинам: один почти бесполезен, другойпочти заменен.

3 голосов
/ 29 июля 2011

Есть ли какой-либо вариант использования функции class inside после введения лямбды?

Определенно.Наличие класса внутри функции примерно таково:

  • локализация его как частной детали реализации кода, намеревающегося его использовать,
  • , предотвращающего использование другого кода и становящегося зависимым от него,
  • не зависит от внешнего пространства имен.

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

Вы можете думать об этом как об обратном наличии частных функций в классе: в этой ситуации внешний API является открытым интерфейсом класса, а функция остается приватной.В этой ситуации функция использует класс как частную деталь реализации, и последняя также остается частной.C ++ является мультипарадигмальным языком и, соответственно, дает такую ​​гибкость при моделировании иерархии организации программы и использования API.

Примеры:

  • функция имеет дело с некоторыми внешними данными (подумайтефайл, сеть, разделяемая память ...) и хочет использовать класс для представления структуры двоичных данных во время ввода-вывода;он может решить сделать этот класс локальным, если он имеет только несколько полей и бесполезен для других функций
  • функция хочет сгруппировать несколько элементов и выделить их массив для поддержки своих внутренних вычисленийделает, чтобы получить его возвращаемое значение;он может создать простую структуру, чтобы обернуть их.
  • классу дано неприятное побитовое перечисление, или, возможно, он хочет переосмыслить float или double для доступа к mantisa / exponent / sign, ирешает для внутреннего использования смоделировать значение, используя struct с битовыми полями подходящей ширины для удобства (примечание: поведение, определяемое реализацией)

классы, определенные в функциях, не позволяют использовать их в шаблонах

Я думаю, вы прокомментировали, что чей-то ответ объяснил это, но в любом случае ...

void f()
{
    struct X { };
    std::vector<X> xs;  // NOPE, X is local
}
3 голосов
/ 29 июля 2011

Определение структур внутри функций никогда не было особенно хорошим способом справиться с отсутствием предикатов. Это работает, если у вас есть виртуальная база, но это все еще довольно уродливый способ иметь дело с вещами. Это может выглядеть примерно так:

struct virtual_base {
  virtual void operator()() = 0;
};

void foo() {
  struct impl : public virtual_base {
    void operator()() { /* ... */ }
  };

  register_callback(new impl);
}

Вы можете продолжать использовать эти классы внутри функций, если хотите, конечно - они не устарели и не повреждены; они были просто ограничены с самого начала. Например, этот код недопустим в версиях C ++ до C ++ 0x:

void foo() {
  struct x { /* ... */ };
  std::vector<x> y; // illegal; x is a class defined in a function
  boost::function<void()> z = x(); // illegal; x is used to instantiate a templated constructor of boost::function
}

Этот вид использования фактически был разрешен в C ++ 0x, поэтому полезность внутренних классов была расширена. Тем не менее, в большинстве случаев это по-прежнему не очень хороший способ.

2 голосов
/ 29 июля 2011

Как упоминал Тони, класс внутри функции - это не только предикаты.Помимо других вариантов использования, он позволяет создать фабричную функцию, которая создает объекты, подтверждающие интерфейс, не раскрывая реализующий класс.Смотрите этот пример:

#include <iostream>
/* I think i found this "trick" in [Alexandrescu, Modern C++ Design] */
class MyInterface {
    public:
        virtual void doSomethingUseful() = 0;
};

MyInterface* factory() {
    class HiddenImplementation : public MyInterface {
        void doSomethingUseful () {
            std::cout << "Hello, World!" << std::endl;
        }
    };

    return new HiddenImplementation();
}


int main () {
    auto someInstance = factory();

    someInstance->doSomethingUseful();
}
2 голосов
/ 29 июля 2011

Boost.Variant.

Лямбды не работают с вариантами, поскольку вариантам нужны объекты, которые имеют более одного оператора () (или имеют оператор шаблона ()). C ++ 0x теперь позволяет использовать локальные классы в шаблонах, поэтому boost::apply_variant может принимать их.

...