【问题标题】:Allowing access to private members允许访问私人成员
【发布时间】:2013-02-13 04:01:30
【问题描述】:

这个问题在某种程度上是我发布的this one 的延续。

我想做的事:我的意思是允许在派生类B 中访问基类A 的私有成员,但有以下限制:

  • 我要访问的是一个结构——实际上是一个std::map<>——而不是一个方法;
  • 无法修改基类;
  • 基类A 没有模板化方法,我可以作为后门替代方案重载——而且我不会添加这样的方法,因为它会反对第二个限制。

作为一种可能的解决方案,我被指出了 litb 的解决方案 (post / blog),但是,就我的一生而言,我无法理解这些帖子中所做的事情,因此,我无法找到解决问题的方法。

我正在尝试做的事情:来自 litb 解决方案的以下代码提供了一种有关如何从类/结构访问私有成员的方法,它恰好涵盖了我的限制提到了。

所以,我正在尝试重新排列这段代码:

template<typename Tag, typename Tag::type M>
struct Rob { 
  friend typename Tag::type get(Tag) {
    return M;
  }
};

// use
struct A {
  A(int a):a(a) { }
private:
  int a;
};

// tag used to access A::a
struct A_f { 
  typedef int A::*type;
  friend type get(A_f);
};

template struct Rob<A_f, &A::a>;

int main() {
  A a(42);
  std::cout << "proof: " << a.*get(A_f()) << std::endl;
}

为了让我能够执行以下操作——注意我即将继承该类,因为在派生类 B 初始化之后立即添加了 std::map&lt;&gt; 中的条目,即std::map&lt;&gt; 不仅仅是 A 类的静态成员,具有默认值,因此我需要从 B 的特定实例中访问它:

// NOT MY CODE -- library <a.h>
class A {
private:
    std::map<int, int> A_map;
};

// MY CODE -- module "b.h"
# include <a.h>
class B : private A {
public:
    inline void uncover() {
        for (auto it(A_map.begin()); it != A_map.end(); ++it) {
            std::cout << it->first << " - " << it->second << std::endl;
        }
    }
};

我想要的答案:我真的喜欢让上面的代码工作——经过适当的修改——但我会非常满足于解释第一个代码块中所做的事情 - 来自 litb 的解决方案。

【问题讨论】:

  • 为什么需要得出自己的解决方案?使用 litb 的代码作为库。你想要一个更易于管理的版本吗?我在某一时刻更好地封装了它。但我认为,如果没记错的话,他实际上有一个独立的版本。
  • @Potatoswatter 这里的问题是我什至不知道该解决方案是否适用于我的问题。我无法在我的代码中实际使用它——主要是因为我还没有真正理解在 libt 的解决方案中做了什么。

标签: c++ class templates inheritance private


【解决方案1】:

这个成语我知道的最好的打包版本如下:

template<class Tag,typename Tag::type MemberPtr>
struct access_cast{
 friend typename Tag::type get(Tag){return MemberPtr;};
};

template<class Tag,class MemberPtr>
struct access_tag{
 typedef MemberPtr type;
 friend type get(Tag);
};

class A {
public:
 auto x() const {return x_;};
private: 
 int x_ = 9;
};

#include <iostream>

struct AMemTag: access_tag<AMemTag,int A::*>{}; //declare tag
template struct access_cast<AMemTag,&A::x_>; //define friend get function

int main() {
 A a;
 std::cout<<a.x()<<"\n";
 a.*get(AMemTag()) = 4; //dereference returned member pointer and modify value
 std::cout<<a.x()<<"\n";
}

See it work.

【讨论】:

    【解决方案2】:

    不幸的是,博客文章及其代码有点不清楚。这个概念很简单:显式模板实例化可以免费在后台传递给任何类,因为

    • 库类的显式实例化可能是客户端类的实现细节,并且
    • 只能在命名空间范围内声明显式实例化。

    分发此后台通行证的自然方式是作为指向成员的指针。如果您有一个指向给定类成员的指针,则可以在该类的任何对象中访问它,而不管访问资格如何。幸运的是,即使在 C++03 中,指向成员的指针也可以是编译时常量。

    所以,我们想要一个在显式实例化时生成指向成员的指针的类。

    显式实例化只是定义类的一种方式。仅仅生成一个类 do 怎么可能?有两种选择:

    • 定义一个friend 函数,它不是类的成员。这就是 litb 的作用。
    • 定义一个静态数据成员,它在启动时被初始化。这是我的风格。

    我会先介绍我的风格,然后讨论它的缺点,然后修改它以匹配 litb 的机制。最终结果仍然比博客中的代码简单。

    简单版。

    该类接受三个模板参数:受限成员的类型、其实际名称以及对全局变量的引用以接收指向它的指针。该类调度一个静态对象进行初始化,其构造函数初始化全局。

    template< typename type, type value, type & receiver >
    class access_bypass {
        static struct mover {
            mover()
                { receiver = value; }
        } m;
    };
    
    template< typename type, type value, type & receiver >
    typename access_bypass< type, value, receiver >::mover
        access_bypass< type, value, receiver >::m;
    

    用法:

    type_of_private_member target::* backstage_pass;
    template class access_bypass <
        type_of_private_member target::*,
        & target::member_name,
        backstage_pass
    >;
    
    target t;
    t.* backstage_pass = blah;
    

    See it work.

    不幸的是,在程序进入main 之前,您不能依赖此结果可用于其他源文件中的全局对象构造函数,因为没有标准的方法来告诉编译器初始化文件的顺序。但是全局变量是按照它们声明的顺序初始化的,所以你可以把你的绕过放在顶部,只要静态对象构造函数不对其他文件进行函数调用,你就可以了。

    强大的版本。

    这通过添加标签结构和friend函数从litb的代码中借用了一个元素,但这是一个小的修改,我认为它仍然很清楚,并不比上面的差很多。

    template< typename type, type value, typename tag >
    class access_bypass {
        friend type get( tag )
            { return value; }
    };
    

    用法:

    struct backstage_pass {}; // now this is a dummy structure, not an object!
    type_of_private_member target::* get( backstage_pass ); // declare fn to call
    
    // Explicitly instantiating the class generates the fn declared above.
    template class access_bypass <
        type_of_private_member target::*,
        & target::member_name,
        backstage_pass
    >;
    
    target t;
    t.* get( backstage_pass() ) = blah;
    

    See it work.

    这个健壮的版本和 litb 的博客文章之间的主要区别在于,我将所有参数收集到一个地方并将标签结构设为空。它只是相同机制的更简洁的接口。但是您必须声明 get 函数,博客代码会自动执行该函数。

    【讨论】:

    • 不幸的是,您不能只将绕过的东西放在 TU 的顶部。因为静态数据成员是一个实例化的实体,所以排序规则不适用。
    【解决方案3】:

    好的,所以你问了如何让那个奇怪的“Rob”代码与你的用例一起工作,所以就是这样。

    // the magic robber
    template<typename Tag, typename Tag::type M>
    struct Rob {
        friend typename Tag::type get(Tag) {
            return M;
        }
    };
    
    // the class you can't modify
    class A {
    private:
        std::map<int, int> A_map;
    };
    
    struct A_f {
        typedef std::map<int, int> A::*type;
        friend type get(A_f);
    };
    
    template struct Rob<A_f, &A::A_map>;
    
    class B : private A {
    public:
        inline void uncover() {
            std::map<int, int>::iterator it = (this->*get(A_f())).begin();
        }
    };
    

    现在,我个人认为这里的治疗方法可能比疾病更糟糕,尽管我通常是你看到的最后一个声称滥用 C++ 是可以的人。你可以自己决定,所以我把这个作为一个单独的答案发布,与我使用预处理器以老式方式完成的答案不同。

    编辑:

    工作原理

    在这里,我将复制上面的代码,但简化了类型,绘制了更多代码,并使用了大量的 cmets。请注意,在进行此练习之前,我对代码的理解不是很透彻,现在我还没有完全理解它,明天我当然不会记得它是如何工作的。警告维护者。

    这是我们不允许更改的代码,使用私有成员:

    // we can use any type of value, but int is simple
    typedef int value_type;
    
    // this structure holds value securely.  we think.
    struct FortKnox {
        FortKnox() : value(0) {}
    private:
        value_type value;
    };
    

    现在开始抢劫:

    // define a type which is a pointer to the member we want to steal
    typedef value_type FortKnox::* stolen_mem_ptr;
    
    // this guy is sort of dumb, but he knows a backdoor in the standard
    template<typename AccompliceTag, stolen_mem_ptr MemPtr>
    struct Robber {
        friend stolen_mem_ptr steal(AccompliceTag) {
            return MemPtr; // the only reason we exist: to expose the goods
        }
    };
    
    // this guy doesn't know how to get the value, but he has a friend who does
    struct Accomplice {
        friend stolen_mem_ptr steal(Accomplice);
    };
    
    // explicit instantiation ignores private access specifier on value
    // we cannot create an object of this type, because the value is inaccessible
    // but we can, thanks to the C++ standard, use this value in this specific way
    template struct Robber<Accomplice, &FortKnox::value>;
    
    // here we create something based on secure principles, but which is not secure
    class FortKnoxFacade : private FortKnox {
    public:
        value_type get_value() const {
            // prepare to steal the value
            // this theft can only be perpetrated by using an accomplice
            stolen_mem_ptr accessor = steal(Accomplice()); // it's over now
            // dereference the pointer-to-member, using this as the target
            return this->*accessor;
        }
    };
    
    int main() {
        FortKnoxFacade fort;
        return fort.get_value();
    }
    

    【讨论】:

    • +1 有效! \o/ 非常感谢!您介意一步一步地做一个浅层的,就像对这里所做的事情的解释一样。我真的不明白typename Tag::type Mfriend type get(A_F) 的所有东西。
    • 我已经编辑了我的答案以添加更多细节。然而,我这样做是有一些保留的,因为我认为这整个方法会导致继承你的代码的人诅咒你的名字——也许比你诅咒让你陷入困境的库作者更粗俗。
    • @Rubens 和 John,请参阅我的答案,以获得更简单的做同样事情的方式,这是我前段时间制定的。如果您想要更深入的描述,请告诉我,但它应该比原来的更容易理解,因为它确实有相当混乱的数据流。
    • @potatoswatter 无论是更简单还是更简洁都在旁观者的眼中 :-) 你的版本可以更容易解释,因为它不需要涵盖 ADL。但是成员的类型必须提到两次。它并不像从基类中派生标签类型那么简单(可以解决您随后得到的愚蠢的 gcc 警告)。也就是说,我确实喜欢你的解释。
    • @JohnZwinck 和大家,谢谢大家的耐心和非常有帮助的解释。希望我能接受所有答案,但我不能,所以我用我的伪随机化选择了一个。再次感谢! (:
    【解决方案4】:

    来点更残忍的怎么样?

    // MY CODE -- module "b.h"
    # define private protected
    # include <a.h>
    # undef private
    class B : private A {
        // now you can access "private" members and methods in A
    

    【讨论】:

    • 这就是我们所说的未定义行为。
    • @Rubens,它必须。预处理器用protected 替换每个private - 它没有reserved words 概念。之后,您的 c++ 编译器会看到 protected 应该是 private 的位置。然后它将允许您访问来自a.h 的所有私有 方法/变量。呵呵
    • @Rubens:是的,它确实有效。我从来没有遇到过会失败的系统,我怀疑 OP 也会失败。也就是说,Potatoswatter 是对的,它并不完全符合犹太教规。但这个问题没有任何意义!
    • 它可以重新排列数据,因为具有相同可访问性的连续数据成员需要位于递增地址
    • 如果标题 &lt;a.h&gt; 定义了一个类如 class foo { int bar; }; ---没有 private 关键字--- 那么 bar 仍然是私有的。要解决它也#define class struct
    猜你喜欢
    • 2013-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多