В соответствии с этим правилом 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
Две реализации имеют немного разные принципы. Первый можно рассматривать как использование уязвимостей связывания, но последний действительно является полностью стандартной реализацией.
И этот синтаксис нельзя проверить простым поиском по ключевым словам, потому что будет много обычных явных экземпляров, которые можно запутать.
Предполагая, что это не ошибка компилятора... и что? Существует множество способов взломать инкапсуляцию, как легально, так и через надежный UB. private
предназначен для предотвращения случайного неправильного использования, а не преднамеренного вероломства.
Я ожидал найти его здесь, но пока не нашел. eel.is/c++draft/temp.explicit#:explicit-instantiation. Я думаю, за исключением «эй, я могу сломать инкапсуляцию», что на самом деле не является сюрпризом, это все еще интересный вопрос, но не тогда, когда неясно, какое именно «правило» вы имеете в виду.
"... типы параметров и возвращаемые типы могут быть приватными"?!? Здесь нет частных типов возврата. Тип возвращаемого значения — int
, а параметр — A
. Я подозреваю, что заголовок — это просто ваша попытка объяснить, почему код компилируется. В любом случае вы должны уточнить, откуда вы взяли это правило
@NicolBolas Можете ли вы сказать мне, если возможно, есть ли другой способ нарушить инкапсуляцию в соответствии с безопасным синтаксисом. Другое правило - не изменять исходный тип, не повторять определение исходного типа.
Специализированный шаблон функции-члена — еще один совершенно законный способ, вот он: godbolt.org/z/WEPGE8Gb4
Поведение, о котором вы говорите, указано в [temp.spec.general]/6
Это правило существует с C++98, то есть с момента первой стандартизации C++. Это как-то не ново для C++17.
Правило тоже нужно. Предположим, вы хотите использовать Access<&A::data_>
внутри A
, где A::data_
доступно, но затем вы не хотите, чтобы Access<&A::data_>
создавался неявно, а только явно создавался в одной единице перевода. Поскольку явное объявление создания экземпляра не может появиться внутри области действия A
, не может быть никакой проверки доступности. Это сделало бы такое явное создание экземпляра совершенно невозможным.
То же самое относится и к явной и частичной специализации.
То, что вы можете использовать дополнительные методы, такие как внедрение друзей, чтобы затем получить доступ к частному члену, не является проблемой, поскольку это не то, что может произойти случайно. Если пользователь решил получить доступ к члену private
, то в любом случае его ничто не остановит. (Например, они могут просто добавить свой собственный класс/функцию в качестве friend
в определение класса, что может быть или не быть нарушением ODR само по себе, но на практике не будет иметь никаких негативных последствий как таковых.)
Цель управления доступом состоит только в том, чтобы сделать так, чтобы пользователь случайно не использовал член, который он не должен использовать напрямую, и простое создание экземпляра с закрытым членом в качестве аргумента не позволяет получить доступ к частному члену. Это требует дополнительной целенаправленной работы. И если пользователь делает всю эту работу, чтобы добраться до закрытого члена, то пользователь намеренно нарушает предполагаемое использование класса, и это будет на них, если результатом будет сломанная программа.
И в любом случае явное создание экземпляров — это всего лишь подсказка по оптимизации; его выполнение не позволяет программисту делать что-либо, что в противном случае было бы невозможно, даже если он ограничен кодом вне класса, написанным в подмножестве C++, защищенном от памяти и не использующем битовые приведения (фактически то же самое подмножество, которое доступно в constexpr
контексте)
можно ссылку на правило? Это из стандарта?