Контроль доступа к члену для функции-друга, определенной внутри класса в C ++ - PullRequest
6 голосов
/ 15 апреля 2020

Я понимаю, что следующий фрагмент кода C ++ должен привести к ошибке в определении g, потому что p.t.x является закрытым и не может быть доступен там.

class P {
  class T {
    int x;
    friend class P;
  };
  T t;

  friend void g(P &p);
};

void g(P &p) { p.t.x = 42; }

Что меня удивляет, так это следующее сниппет. Он отличается только тем, что определение функции друга g теперь происходит внутри class P.

class P {
  class T {
    int x;
    friend class P;
  };
  T t;

  friend void g(P &p) { p.t.x = 42; }
};

Clang ++ (оба 6.0. 0-1ubuntu2 и версия Apple clang-1100.0.33.8) компилирует последнюю без ошибок, тогда как GNU C ++ (7.5.0-3ubuntu1 ~ 18.04) выдает ту же ошибку, что и в предыдущем фрагменте.

Я понимаю, что функция g, определенная в последнем случае, не относится к той же области, что и определенная в первом случае (см. связанный вопрос и более старое более длинное обсуждение ), и это только видно через ADL. Но я думаю, что здесь я спрашиваю иначе: должно ли объявление friend class P в классе T распространяться на тело функции друга g или нет?

Соответствующая часть стандарта C ++ ( В §11.3 или §11.9.3 в более поздних drafts ) говорится, что:

7 ... Функция друга, определенная в классе, входит в (лексическую) область видимости класс, в котором он определен. Друга функция, определенная вне класса, не является (6.5.1).

Так что я понимаю, что Clang ++ и GNU C ++ по-разному интерпретируют то, что подразумевается под "лексической областью действия" (см. Также этот ответ к предыдущему связанному вопросу). Clang ++, похоже, компилирует g, как если бы он был другом класса T, возможно потому, что он входит в лексическую область класса P, который является другом класса T, тогда как GNU C ++ этого не делает.

  1. Есть ли ошибка в одном из двух компиляторов?
  2. Если да, то какой?
  3. Независимо от ответов на предыдущие вопросы, разве это не то, что стандарт должен формализоваться лучше?

Ответы [ 2 ]

2 голосов
/ 15 апреля 2020

Это выглядит как CWG1699 (который все еще открыт).

1699. Подружится ли класс с друзьями?

Согласно параграфу 2 14.3 [class.friend],

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

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

  class c {
    class n {};
    friend struct s;
  };

  struct s {
    // #1 and #2 are not relevant for this question
    friend void f() { c::n(); } // #3
  }; 

В частности, если в определении класса определена функция друга, как в # 3, имеет ли ее определение доступ к закрытым и защищенным членам класса поддержки? Реализации варьируются по этому вопросу.

0 голосов
/ 15 апреля 2020

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

tl; др

  1. Да.
  2. IMO clang ++
  3. Если вы посмотрите ниже, вы заметите, что это формализовано довольно хорошо.

Полный ответ

Сказав, что я думаю, что есть две соответствующие цитаты из стандарта. Во-первых, это:

11.9 Контроль доступа к элементу

1 Членом класса может быть

(1.1) - private; то есть его имя могут использовать только члены и друзья класса, в котором оно объявлено.

, а второе:

11.9.3 Друзья [class.friend]

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

Из этих двух можно сделать несколько выводов. Но наиболее важным является то, что даже встроенный друг не может получить доступ к закрытым членам класса, определенным внутри класса, к которому относятся друзья. Почему? Потому что он не является ни членом, ни другом вложенного класса. И из этого кажется, что g ++ прямо здесь.

...