Сохранить права доступа при использовании оператора '->'
У меня два класса,
template<class Type>
class SafePtr {
public:
SafePtr() {}
~SafePtr() {}
void Lock(Type* data, void* key)
{
if (!pKey)
{
pKey = key;
pData = data;
}
}
Type* Unlock(void* key) const
{
if (key == pKey)
return pData;
}
Type* operator->()
{
return pData;
}
private:
Type* pData = nullptr;
void* pKey = nullptr;
};
template<class Type>
class SafePtrArray {
public:
SafePtrArray() {}
~SafePtrArray() {}
template<class... Args>
SafePtr<Type> CreatePtr(Args&&... args)
{
Type* data = new Type(args...);
ptrs.insert(ptrs.end(), data);
SafePtr<Type> ptr;
ptr.Lock(data, this);
return ptr;
}
Type* UnlockPtr(const SafePtr<int>& ptr)
{
return ptr.Unlock(this);
}
void Destroy(const SafePtr<int>& ptr)
{
Type* pointer = ptr.Unlock(this);
for (auto itr = ptrs.begin(); itr != ptrs.end(); itr++)
{
if ((*itr) == pointer)
{
delete pointer;
ptrs.erase(itr);
}
}
}
private:
std::vector<Type*> ptrs;
};
Объяснение:
Цель состоит в том, чтобы защитить указатель, чтобы пользователь мог получить доступ к его элементам, но не мог манипулировать его фактическим указателем (в основном, преждевременно удалять его). А также мне нужно хранить все указатели в массиве, чтобы при уничтожении родительского объекта я мог автоматически уничтожить все выделенные указатели.
Для этого я использую два класса и .
Сначала все работало нормально, но вскоре я обнаружил эту ошибку,
int main()
{
SafePtrArray<int> ptr;
auto pInt = ptr.CreatePtr();
int* i = pInt.operator->(); // Users can get access to the underlying pointer using this.
ptr.Destroy(pInt);
}
Итак, мой вопрос: есть
ли способ запретить пользователям доступ к базовому типу и запретить им манипулировать указателем, имея привилегию доступа к его членам?
2 ответа
Я все еще думаю, что вы пытаетесь решить проблему, которая больше связана с возможными недостатками в дизайне API/кода, документации или с отсутствием знаний C++ у того, кто его использует, с «решением», которое имеет больше минусов, чем плюсов.
Если программист C++ не знает, что такое право собственности, или не уважает его, и вслепую удаляет объекты или освобождает память указателей, тогда проблем будет гораздо больше. Скорее всего, вы перенесете проблему просто в другую часть кода.
С учетом сказанного, самое близкое, что вы можете сделать, чтобы не показывать указатель прямо сейчас, это что-то вроде этого:
(Код — это всего лишь доказательство концепции, поэтому такие вещи, как
call
может надо доработать)
#include <iostream>
#include <string>
struct Test {
void foo(int x, int y, std::string str) {
std::cout << x << " " << y << " " << str << std::endl;
}
double test = 0.5;
};
template <typename T>
struct Ptr {
template <auto M, typename... Args>
auto call(Args... args) {
return (obj.*M)(std::forward<Args>(args)...);
}
template <auto M>
auto get() {
return (obj.*M);
}
protected:
T obj;
};
int main() {
Ptr<Test> p;
p.call<&Test::foo>(1, 2, "hello");
std::cout << p.get<&Test::test>() << std::endl;
return 0;
}
Но я все еще не думаю, что это хороший подход.
И пользователь все еще может возиться с кодом и сделать что-то плохое, например:
int main() {
Ptr<Test> p;
delete &p;
return 0;
}
Или это, что, безусловно, является неопределенным поведением, но на самом деле это не имеет значения, поскольку удаление не принадлежащего объекта также приведет к неопределенному поведению в какой-то момент:
template<typename T>
struct Ptr {
protected:
T *obj;
}
template<typename T>
struct Ptr2 {
public:
T *obj;
};
int main()
{
Ptr<Test> p;
Ptr2<Test> *p2 = reinterpret_cast<Ptr2<Test>*>(&p);
std::cout << p2->obj << std::endl;
}
Так что нет защиты опять же от таких вещей.
Помимо показанного кода, есть предложение для отражения , которое уже полностью завершено, что позволило бы получать информацию о членах типа, но это не было добавлено в c++20, и одно для метаклассов , которого также нет в стандарт еще.
С этими двумя предложениями вы могли бы реализовать что-то более полезное. Но мои опасения по поводу преимуществ этого остаются.
Есть ли способ запретить пользователям доступ к базовому типу и запретить им манипулировать указателем, имея привилегию доступа к его членам?
При определенных условиях нет, это невозможно. Если в основе
reinterpret_cast
, что позволяет вызывать
Для классов с нестандартной компоновкой, вероятно, существуют специфичные для компилятора (непереносимые) методы для достижения того же эффекта (преобразование адреса члена данных в указатель на базовый объект). У компилятора нет причин активно пытаться помешать таким вещам.
Если программист решил вызвать неопределенное поведение, вы мало что можете сделать, чтобы остановить его.