Имеет ли использование лямбда-функций для симуляции лексических ограничений непредвиденные проблемы производительности / реализации?

Программа 1:

#include <iostream>

std::string Hello(void){return "Hello";}
std::string World(void){return "world!";}

int main(){

    std::cout << Hello() << " " << World() << std::endl;
}

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

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

Я пытаюсь следовать принципу функциональной декомпозиции задачи на подзадачи, и, таким образом, не имеет смысла когда-либо вызывать определенные функции вне области действия другой конкретной функции.

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

Программа 2:

#include <iostream>

int main(){

    auto Hello = [](void) -> std::string{return "Hello";};
    auto World = [](void) -> std::string{return "world!";};

    std::cout << Hello() << " " << World() << std::endl;
}

Hello() и World() инкапсулированы внутри main() и не могут быть вызваны вне области действия main().
Это все по-другому?

3 ответа

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

Например, рассмотрим модуль, состоящий из заголовочного файла:

// List dependencies
#include <string>

// It's customary to put things in a namespace
// Note that namespaces are commonly cross-module

namespace ns {

// Declarations visible to all
std::string hello_world();

} // ns

и такая реализация:

// include header listed above
#include <header.hpp>

// This contains declarations visible to this module only
namespace {

std::string hello_part();

// This is both a declaration and a definition
std::string world_part()
{ return "world!"; }

} // namespace

// Definition of function declared in header
std::string ns::hello_world()
{
    // Implemention defers to private parts that are visible here
    return hello_part() + " " + world_part();
}

// Definition of private function
// We need to reopen the unnamed namespace for that

namespace {

std::string hello_part()
{ return "Hello"; }

} // namespace

Теперь другой модуль может объявить другой hello_part сами по себе, которые не обязательно должны быть функциями с тем же объявлением или даже функцией вообще, и конфликта не будет. Там будут две разные и отдельные сущности - очень похоже на for Заявление может иметь свое int i; переменная с вмешательством без каких-либо других i!

Что касается другой части вашего вопроса, это не имеет большого значения, если hello_part а также world_part Выше реализованы как простые старые функции или как функциональные объекты (с помощью лямбда-выражений или иным образом). Но имейте в виду, что, поскольку они не закрываются ни над чем, они не слишком закрыты. Лямбда-выражения C++ позволяют закрывать все, что угодно, включая локальные переменные (также известные как локальные захваты), но система типов не достаточно выразительна, чтобы учесть проблему восходящего funarg, не выплачивая небольшой штраф в определенных ситуациях. Хотя это верно для веры C++, это не может произойти за вашей спиной, и вам нужно будет прописать это наказание через, например, std::function, В этих случаях я бы поспорил с лямбда-выражениями, так как считаю, что есть более удачные альтернативы (на которых я не буду здесь останавливаться).

В общем, решение о том, хотите ли вы использовать лямбда-выражение, является соображением, которое не повлияет (и не должно) повлиять на модульность вашей программы.

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

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

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

До C++11 и lambdas были очень большие проекты, и риск коллизий управлялся различными средствами, включая пространства имен, как уже упоминалось DeadMG, но также классы и объектно-ориентированный дизайн и другие формы инкапсуляции (определяем локальные функции как static на уровне пространства имен в файле реализации, форсируя внутреннюю связь и избегая конфликтов с другими единицами перевода. Кроме того, если вы тщательно выбираете значимые имена для своих идентификаторов, вам следует избегать 99% коллизий.

Если вы действительно собираетесь работать над крупномасштабным проектом, подумайте о разработке программного обеспечения John Lakos Large Scale C++.

Другие вопросы по тегам