В чем разница между & C :: c и & (C :: c)?

Проверьте код ниже, и я поставил информацию о выходе в комментарии. Я использовал gcc 4.8.5 и Centos 7.2.

#include <iostream>
#include <cstdio>

class C 
{
    public:
        void foo() {
            printf("%p, %p\n", &C::c, &(C::c)); // output value is 0x4, 0x7ffc2e7f52e8
            std::cout << &C::c << std::endl;    // output value is 1
        }
        int a;
        int c;
};

int main(void)
{
    C co;

    printf("%p\n", &C::c);              // output value is 0x4
    std::cout << &C::c << std::endl;    // output value is 1

//    printf("%p\n", &(C::c));   // compile error, invalid use of non-static data member 'C::c'

    co.foo();

    return 0;
}
  1. Согласно C++ оператору Precedence operator оператор :: имеет более высокий приоритет, чем оператор &. Я думаю, что &C::c равен &(C::c), но результат говорит об обратном. Почему они разные?
  2. &(C::c) вызывает ошибку компиляции в main, но не в функции foo Почему?
  3. Значение &C::c отличается в printf и std::cout, почему это так?

Ответы

Ответ 1

C++ различает два вида операндов для оператора &, l значения в целом и (квалифицированные) идентификаторы в частности. В &C::c операнд & является квалифицированным идентификатором (т.е. Просто именем), тогда как в &(C::c) операнд является общим выражением (потому что ( не может быть частью имени).

Форма квалифицированного идентификатора имеет особый случай: если она ссылается на нестатический член класса (например, ваш C::c), & возвращает специальное значение, известное как "указатель на член C". Смотрите здесь для получения дополнительной информации об указателях участников.

В &(C::c) нет особого случая. C::c разрешается нормально и дает сбой, потому что нет объекта, для которого нужно получить член c. По крайней мере, то, что происходит в main; в методах C (например, ваш foo) есть неявный this объект, поэтому C::c фактически означает this->c там.

Что касается того, почему выходные данные отличаются для printf и cout: когда вы пытаетесь напечатать указатель на член с помощью <<, он неявно преобразуется в bool, возвращая false если он является нулевым указателем, и true противном случае. false печатается как 0; true печатается как 1. Ваш член указатель не является нулевым, поэтому вы получаете 1. Это отличается от обычных указателей, которые неявно преобразуются в void * и печатаются как адреса, но указатели-члены не могут быть преобразованы в void * поэтому единственной применимой перегрузкой operator<< является та, что для bool. См. Https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt#Notes.

Обратите внимание, что технически ваши вызовы printf имеют неопределенное поведение. %p занимает void * и вы передаете ему указатели разных типов. При нормальных вызовах функций срабатывает автоматическое преобразование из T * в void *, но printf - это функция переменных аргументов, которая не предоставляет контекста типа своему списку аргументов, поэтому вам нужно ручное преобразование:

printf("%p\n", static_cast<void *>(&(C::c)));

Соответствующей частью стандарта является [expr.unary.op], где говорится:

Результатом унарного оператора & является указатель на его операнд. Операндом должно быть lvalue или квалифицированный идентификатор. Если операндом является квалифицированный идентификатор, называющий нестатический или вариантный член m некоторого класса C с типом T, результат имеет тип "указатель на член класса C типа T " и является предварительным значением, обозначающим C​::​m В противном случае, если тип выражения T, результат имеет тип "указатель на T " [...]

Ответ 2

В то время как выражение &C::c приводит к указателю на член c выражение &(C::c) дает адрес переменной-члена c. Различие, которое вы видите в выводе, состоит в том, что std::cout включает неявное преобразование bool, которое сообщает вам, является ли указатель нулевым или нет.

Поскольку &C::c на самом деле не равно нулю, неявно преобразуется в bool со значением true или 1

Ответ 3

Q1: В синтаксисе & есть специальное значение, за которым следует квалифицированный идентификатор без скобок. Это значит сформировать указатель на член. Кроме того, нет другого способа сформировать указатель на член. Это покрыто С++ 17 [expr.unary.op]/4:

Указатель на член формируется только тогда, когда используется явное & а его операндом является квалифицированный идентификатор, не заключенный в скобки. [Примечание: то есть выражение &(qualified-id), где квалифицированный идентификатор заключен в скобки, не образует выражение типа "указатель на член". Также не квалифицированный идентификатор [...]


Q3: в обоих случаях, когда вы пишете printf("%p\n", &C::c); &C::c является указателем на член. Спецификатор формата %p предназначен только для void * поэтому это вызывает неопределенное поведение, и вывод программы не имеет смысла.

Код cout << &C::c; выводит указатель на член через operator<<(bool val), поскольку существует неявное преобразование указателя на член в bool (с результатом true во всех случаях), см. [conv.bool]/1.

Для дальнейшего обсуждения того, как напечатать указатель на член, см. Этот ответ.


Q2: код &(C::c) не формирует указатель на член, как объяснено выше.

Теперь код C::c находится в грамматической категории id-выражения. (Который является квалифицированным идентификатором и неквалифицированным идентификатором). У id-выражения есть некоторые ограничения по его использованию, [expr.prim.id]/1:

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

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

Когда мы находимся внутри функции C::foo, применяется первый из этих пунктов. Код такой же, как &c но с ненужной квалификацией. Это имеет тип int *. Вы можете вывести это с помощью std::cout << &(C::c); который будет показывать адрес памяти, адрес this->c.

Когда мы находимся в main функции, ни одна из этих трех точек не применяется, и поэтому &(C::c) имеет правильной формы.

Ответ 4

Во-первых, вы не можете получить доступ к int c, используя &(C::c) вне класса. &(C::c) означает адрес памяти " c экземпляра C ", в некоторой степени &(this->c) здесь. Однако ваш c не является статическим членом класса C и не существует экземпляра C Вы также не можете получить доступ к int x = C::c снаружи.

Итак, вы видите ошибку с:

//    printf("%p\n", &(C::c));   // compile error, invalid use of non-static data member 'C::c'

Если у вас есть static int c, то C::c вне класса в порядке, потому что здесь не нужен экземпляр.

И пусть бегут

#include <iostream>
#include <cstdio>

class C
{
    public:
        void foo() {
            printf("%p, %p, this=%p\n", &C::c, &(C::c), this);
        }
        int a;
        int c;
};

int main(void)
{
    C co;
    co.foo();
    return 0;
}

Выход:

0x4, 0x7ffee78e47f4, this=0x7ffee78e47f0
// 0x4 + this == 0x7ffee78e47f4
// see reference

А для std::out: << &C::c неявно приводится к bool, так что true равно 1 вы видели. Вы можете рассматривать &C::c как an offset of c in C, это невозможно использовать без экземпляра C

Все это.

Некоторая ссылка: C++: указатель на член данных класса ":: *"

Полное описание: https://en.cppreference.com/w/cpp/language/pointer