Вопрос, кажется, касается ситуации, когда программисту нужно ввести функцию, которая не работает на любом экземпляре класса (отсюда и возможность выбора static
функция-член). Поэтому я ограничу этот ответ следующей конструкторской ситуацией, когда выбор между статической функцией f()
и функцией без друзей f()
:
struct A
{
static void f(); // Better this...
private:
friend void f(); // ...or this?
static int x;
};
int A::x = 0;
void A::f() // Defines static function
{
cout << x;
}
void f() // Defines friend free function
{
cout << A::x;
}
int main()
{
A::f(); // Invokes static function
f(); // Invokes friend free function
}
Не зная заранее о семантике из f()
и A
(я вернусь к этому позже), у этого ограниченного сценария простой ответ: the static
функция предпочтительна . Я вижу две причины для этого.
ОБЩИЕ АЛГОРИТМЫ:
Основная причина в том, что такой шаблон может быть написан следующим образом:
template<typename T> void g() { T::f(); }
Если бы у нас было два или более классов, имеющих на своем интерфейсе функцию static
f()
, это позволило бы нам написать одну единственную функцию, которая в общем случае вызывает f()
для любого такого класса.
Невозможно написать эквивалентную обобщенную функцию, если мы сделаем f()
бесплатной функцией, не являющейся членом. Хотя это правда, что мы можем поместить f()
в пространство имен, чтобы синтаксис N::f()
мог использоваться для имитации синтаксиса A::f()
, все равно было бы невозможно написать функцию шаблона, такую как g<>()
выше, потому что имена пространств имен не являются допустимыми аргументами шаблона.
ИЗБЫТОЧНЫЕ ДЕКЛАРАЦИИ:
Вторая причина в том, что если бы мы поместили свободную функцию f()
в пространство имен, нам бы не разрешили встроить ее определение непосредственно в определение класса, не вводя никакого другого объявления для f()
struct A
{
static void f() { cout << x; } // OK
private:
friend void N::f() { cout << x; } // ERROR
static int x;
};
Чтобы исправить вышесказанное, мы должны предшествовать определению класса A
следующим объявлением:
namespace N
{
void f(); // Declaration of f() inside namespace N
}
struct A
{
...
private:
friend void N::f() { cout << x; } // OK
...
};
Это, однако, противоречит нашему намерению объявить и определить f()
только в одном месте.
Более того, если бы мы хотели объявить и определить f()
отдельно, сохраняя f()
в пространстве имен, нам все равно пришлось бы ввести объявление для f()
перед определением класса для A
: не сделать этого приведет к тому, что компилятор пожалуется на тот факт, что f()
должен быть объявлен внутри пространства имен N
, прежде чем имя N::f
может быть использовано легально.
Таким образом, теперь у нас будет f()
, упомянутое в трех отдельных местах, а не только в двух (объявление и определение):
- Объявление внутри пространства имен
N
до определения A
;
- Объявление
friend
внутри определения A
;
- Определение
f()
внутри пространства имен N
.
Причина, по которой объявление и определение f()
внутри N
не могут быть объединены (в общем), заключается в том, что f()
должен иметь доступ к внутренним частям A
и, следовательно, к определению A
должно быть видно, когда определено f()
. Тем не менее, как уже было сказано, объявление f()
внутри N
должно быть видно до того, как будет сделано соответствующее объявление friend
внутри A
. Это фактически заставляет нас разделить декларацию и определение f()
.
СЕМАНТИЧЕСКИЕ СООБРАЖЕНИЯ:
В то время как вышеупомянутые два пункта универсально действительны, есть причины, по которым можно предпочесть объявить f()
как static
вместо того, чтобы сделать его friend
из A
или наоборот, что обусловлено вселенной дискурса.
Чтобы уточнить, важно подчеркнуть тот факт, что функция-член класса, будь то static
или не static
, логически является частью этого класса. Это способствует его определению и, таким образом, дает концептуальную характеристику этого.
С другой стороны, функция friend
, несмотря на то, что ей предоставлен доступ к внутренним членам класса, другу которого она является, все еще остается алгоритмом, логически внешним для определения класс.
Функция может быть friend
из более чем одного класса, но может быть членом только одного .
Таким образом, в конкретной области приложения проектировщик может учитывать семантику как функции, так и класса при принятии решения о том, следует ли сделать первый friend
или членом последний (это относится не только к static
функциям, но и к функциям, не относящимся к static
, где могут вмешиваться другие языковые ограничения).
Способствует ли функция логическому определению класса и / или его поведения, или это скорее внешний алгоритм? На этот вопрос невозможно ответить без знания конкретной области приложения.
ВКУС:
Я полагаю, что любой другой аргумент, который только что приведен, проистекает исключительно из вопроса вкуса : как свободный friend
, так и static
членский подход, на самом деле, позволяют четко заявить Интерфейс класса находится в одном месте (определение класса), поэтому по дизайну они эквивалентны (по модулю вышеупомянутых наблюдений, конечно).
Остальные различия являются стилистическими: хотим ли мы написать ключевое слово static
или ключевое слово friend
при объявлении функции, и хотим ли мы написать квалификатор A::
при определении класса вместо определения класса N::
Спецификатор области имен пространства имен. Таким образом, я не буду комментировать дальше.