Почему в С++ есть это правило: явные определения создания экземпляров игнорируют спецификаторы доступа к членам: типы параметров и возвращаемые типы могут быть закрытыми

В соответствии с этим правилом C++ может напрямую разрушать инкапсуляцию в безопасном синтаксисе.

С++ 11

#include <iostream>

class A {
 public:
  A() = default;
 private:
  int data_ = 0;
};

template < int A::*Member >
class Access {
 public:
   friend  int GetPrivateData(A& obj) {
     return obj.*Member;
  }
};

template  class Access<&A::data_>; // explicit instantiation

int GetPrivateData(A& );


int main() {
  A obj;
  GetPrivateData(obj);

  return 0;
}

//https://www.zhihu.com/question/521898260/answer/2876618819

С++ 17

class A {
 public:
  A(int num) : data_(num) {};
 private:
  int data_ = 0;
};

template <typename PtrType>
class Access {
 public:
 inline static PtrType ptr;
};

template <auto T>
struct PtrTaker {
    struct Transferer {
        Transferer() {
            Access<decltype(T)>::ptr = T;
        }
    };
    inline static Transferer tr;
};

template class PtrTaker<&A::data_>; // explicit instantiation

int main() {
  A a{10};

  int b = a.*Access<int A::*>::ptr;
  
  return 0;
}

//https://www.zhihu.com/question/521898260/answer/2876618819

Две реализации имеют немного разные принципы. Первый можно рассматривать как использование уязвимостей связывания, но последний действительно является полностью стандартной реализацией.

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

можно ссылку на правило? Это из стандарта?

463035818_is_not_a_number 06.02.2023 18:43

Предполагая, что это не ошибка компилятора... и что? Существует множество способов взломать инкапсуляцию, как легально, так и через надежный UB. private предназначен для предотвращения случайного неправильного использования, а не преднамеренного вероломства.

Nicol Bolas 06.02.2023 18:46

Я ожидал найти его здесь, но пока не нашел. eel.is/c++draft/temp.explicit#:explicit-instantiation. Я думаю, за исключением «эй, я могу сломать инкапсуляцию», что на самом деле не является сюрпризом, это все еще интересный вопрос, но не тогда, когда неясно, какое именно «правило» вы имеете в виду.

463035818_is_not_a_number 06.02.2023 18:50

"... типы параметров и возвращаемые типы могут быть приватными"?!? Здесь нет частных типов возврата. Тип возвращаемого значения — int, а параметр — A. Я подозреваю, что заголовок — это просто ваша попытка объяснить, почему код компилируется. В любом случае вы должны уточнить, откуда вы взяли это правило

463035818_is_not_a_number 06.02.2023 19:05

@NicolBolas Можете ли вы сказать мне, если возможно, есть ли другой способ нарушить инкапсуляцию в соответствии с безопасным синтаксисом. Другое правило - не изменять исходный тип, не повторять определение исходного типа.

余国良 06.02.2023 20:29

Специализированный шаблон функции-члена — еще один совершенно законный способ, вот он: godbolt.org/z/WEPGE8Gb4

alagner 09.02.2023 09:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
89
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поведение, о котором вы говорите, указано в [temp.spec.general]/6

Это правило существует с C++98, то есть с момента первой стандартизации C++. Это как-то не ново для C++17.

Правило тоже нужно. Предположим, вы хотите использовать Access<&A::data_> внутри A, где A::data_ доступно, но затем вы не хотите, чтобы Access<&A::data_> создавался неявно, а только явно создавался в одной единице перевода. Поскольку явное объявление создания экземпляра не может появиться внутри области действия A, не может быть никакой проверки доступности. Это сделало бы такое явное создание экземпляра совершенно невозможным.

То же самое относится и к явной и частичной специализации.

То, что вы можете использовать дополнительные методы, такие как внедрение друзей, чтобы затем получить доступ к частному члену, не является проблемой, поскольку это не то, что может произойти случайно. Если пользователь решил получить доступ к члену private, то в любом случае его ничто не остановит. (Например, они могут просто добавить свой собственный класс/функцию в качестве friend в определение класса, что может быть или не быть нарушением ODR само по себе, но на практике не будет иметь никаких негативных последствий как таковых.)

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

И в любом случае явное создание экземпляров — это всего лишь подсказка по оптимизации; его выполнение не позволяет программисту делать что-либо, что в противном случае было бы невозможно, даже если он ограничен кодом вне класса, написанным в подмножестве C++, защищенном от памяти и не использующем битовые приведения (фактически то же самое подмножество, которое доступно в constexpr контексте)

user3840170 06.02.2023 20:21

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