Подтвердить что ты не робот

Когда функции должны быть функциями-членами?

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

Например, если имеется некоторый класс A, он будет писать глобальные функции типа:

void foo( A *ptrToA ){}

или

void bar( const A &refToA ){}

Мой первый инстинкт, видя глобальные функции, такие как: "Почему не эти члены A?" Он будет настаивать вверх и вниз, что это согласуется с рекомендациями по хорошей практике на С++, потому что foo и bar могут выполнять все, что им нужно, используя открытый интерфейс A. Например, он будет утверждать, что это полностью согласовано со Скоттом Мейерсом. Эффективные рекомендации на C++. Мне трудно совместить это с пунктом 19 в этой книге, в котором в основном говорится, что все должно быть функцией-членом с несколькими исключениями (оператор < < и operator → и функции, которые требуют преобразования динамического типа). Более того, хотя я согласен с тем, что функции могут делать то, что им нужно делать с публичным интерфейсом A, на мой взгляд, что в основном это результат того, что люди пишут классы, у которых есть получатели и сеттеры для каждого элемента данных класса A. Таким образом, публичный интерфейс, A - сверхпрославленная структура, и вы, безусловно, можете что-либо сделать с открытым интерфейсом. Лично я не думаю, что это должно быть использовано, я думаю, что это должно быть обескуражено.

Очевидно, что это возможно только на языке, таком как С++, который не является чисто объектно-ориентированным, поэтому я предполагаю, что один из способов взглянуть на него заключается в том, что мой коллега не поддерживает чисто объектно-ориентированный подход к разработке программного обеспечения. Кто-нибудь знает какую-либо литературу, которая поддерживает эту позицию в качестве лучшей практики? Или кто-то согласен с этим и может объяснить это мне по-другому, чем мой коллега, чтобы я мог видеть свет? Или все согласны с моим нынешним чувством, что это просто не имеет большого смысла?

Edit: Позвольте мне привести лучший пример кода.

class Car
{
    Wheel frontLeft;
    Wheel frontRight;
    Wheel rearLeft;
    Wheel rearRight;
    Wheel spareInTrunk;

public:
    void wheelsOnCar( list< Wheel > &wheels )
    {
        wheels.push_back( frontLeft );
        wheels.push_back( frontRight);
        wheels.push_back( rearLeft);
        wheels.push_back( rearRight);
    }
    const Wheel & getSpare(){ return spareInTrunk; }
    void setSpare( const Wheel &newSpare ){ spareInTrunk = newSpare; }
    // There are getters and setters for the other wheels too,
    //but they aren't important for this example
};

Затем я увижу такую ​​функцию:

void wheelsRelatedToCar( Car *aCar, list< Wheel > &wheels )
{
    aCar->wheelsOnCar( wheels );
    wheels.push_back( aCar->getSpare() );
}

Это реальный пример с именами классов и функций, которые, конечно, изменились. Почему бы не хотеть wheelsRelatedToCar не быть членом функции Car? В этом реальном примере автомобиль и колесо находились в одной и той же библиотеке. Глобальная функция была определена в исходном файле в конкретном приложении с использованием этой библиотеки, поэтому был сделан аргумент, что функция была специфичной для приложения. Я ответил, что это была совершенно законная операция на Автомобиле и принадлежала классу Car. Есть ли еще одна перспектива взглянуть на нее (кроме тех, кто не предпочитает использовать объектно-ориентированный дизайн)?

4b9b3361

Ответ 1

Скотт Мейерс выступал за то, чтобы функции, не являющиеся членами, часто улучшали инкапсуляцию:

Herb Sutter и Jim Hyslop также говорят об этом (цитируя статью Майера) в "Self-Sufficient Headers"

Эти идеи были переизданы (в более утонченной форме) в 3-е издание Meyer "Эффективный С++" , "Пункт 23: -member функции, отличные от друга, для функций-членов", и Sutter/Alexandrescu "Стандарты кодирования С++" , "44 - Предпочитаете писать функции non -ember nonfriend".

Я думаю, что многие разработчики находят это неинтуитивным и, возможно, немного спорным.

Ответ 2

Херб Саттер и Андрей Александреску рекомендуют это:

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


Функции, не относящиеся к членству:

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

Теперь, чтобы ответить на ваш вопрос (, когда?), вот алгоритм, определяющий, должна ли функция быть членом и/или другом:

If the function is one of the operators =, ->, [], or (), which must be members:
=> Make it a member

Else if: 
    a) the function needs a different type as its left-hand argument (as do stream operators, for example); 
 or b) it needs type conversions on its leftmost argument; 
 or c) it can be implemented using the class public interface alone:
=> Make it a nonmember (and friend if needed in cases a) and b) )

If it needs to behave virtually:
=> Add a virtual member function to provide the virtual behavior, and implement the nonmember in terms of that

Else: 
=> Make it a member

Литература:

  • Н. Саттер и Андрей Александреску. Стандарты кодирования С++ (Addison-Wesley, 2004).
  • S. Мейерс. "Как функции, отличные от членов, улучшают инкапсуляцию" (Журнал пользователей C/С++, 18 (2), февраль 2000 года).
  • B.Stroustrup. Язык программирования С++ (Special 3rdEdition) (Addison-Wesley, 2000). §10.3.2, §11.3.2, §11.3.5, §11.5.2, §21.2.3.1
  • Н. Саттер. Исключительный С++ (Addison-Wesley, 2000).
  • Н. Саттер. Исключительный стиль С++ (Addison-Wesley, 2004).

Ответ 3

OK

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

Ваш помощник прав, потому что проще создавать общие методы, если они не являются частью класса (например, почему std:: find не является членом std::vector?), потому что тогда он не будет работать для списков, карт и т.д.).

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

Когда вы выйдете в реальный мир и начнете писать приложения, а работайте с коммерческими сроками, вы обнаружите, что каждое приложение является конкурирующим набором требований. "Нам нужно получить A, B и C, но к концу следующего месяца. Это невозможно, они могут иметь B и C, или и C, но не A и B. Или они могут иметь не- идеальную версию A и B и хорошую версию C".

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

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

Надеюсь, что это поможет.

Ответ 4

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

Ответ 5

Один из способов взглянуть на это:

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

Однако это не означает, что это хорошая идея, чтобы исключительно следовать этим рекомендациям во всех случаях. Есть и другие соображения. Например, как было указано другими, функция нечлена, противоречащая популярному убеждению, часто увеличивает инкапсуляцию. (Конечно, если они имеют дело с состоянием объекта с помощью геттеров/сеттеров для частных данных, то это более чем сомнительно. Фактически, я все равно считаю, что геттеры/сеттеры сомнительны. См. эта отличная статья на эту тему.)

Ответ 6

В дополнение к уже упомянутым статьям, я думаю, что стоит процитировать несколько дополнительных мнений экспертов:


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


  • Бьярне Страуструп. Язык программирования С++:

10.3.2 Вспомогательные функции

Как правило, класс имеет ряд связанных с ним функций, которые не должны быть определены в классе потому что им не нужен прямой доступ к представлению. Например:

int diff(Date a,Date b); // number of days in the range [a,b) or [b,a)
bool leapyear(int y);
Date next_weekday(Date d);
Date next_saturday(Date d);

Определение таких функций в самом классе осложняло бы интерфейс класса и увеличивало количество функций, которые потенциально должны были бы быть рассмотрены, когда изменение представления было рассмотрено. Как такие функции "связаны" с классом Date? Традиционно их заявления были просто помещенный в тот же файл, что и объявление класса Date, и пользователи, которым нужны Даты, сделают их доступными, включив в него файл, определяющий интерфейс.


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

Ответ 7

Ваш коллега прав. wheelsRelatedToCar не должен быть функцией-членом class Car, потому что эта операция даже не применяется (только) к Авто. Эта функция делает что-то с Car и что-то еще со списком колес.

Итак, это должна быть функция-член другого класса более высокого уровня, такого как class Mechanic или class WheelReplacementOperation. И в случае, если у вас нет такого класса (хотя, я думаю, вы должны иметь!), Лучше сделайте эту функцию глобальной.

Ответ 8

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

Ответ 9

Итак, вы говорите, что String не должен иметь открытый элемент "bool IsEmpty() const" только потому, что он имеет "int Size() const" и IsEmpty определен и реализован как Size() == 0?

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

Ответ 10

Часть решения сделать что-то членом или нечленом также связано со сложностью и сложностью управления.

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

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

Например, может быть объект документа, но класс редактора, который работает на нем, и содержит функции редактирования, а не все функции редактирования в документе.

Нет жестких или быстрых правил - просто принципы, такие как инкапсуляция, управление сложностью, сокращение зависимостей. Некоторые из них связаны с компромиссами и должны анализироваться в контексте проблемы, к которой они применяются.