草庐IT

c++ - 检测基类分配给指向派生类的引用

coder 2024-02-05 原文

我目前正在研究多态类型和赋值操作之间的相互作用。我主要担心的是,是否有人可能会尝试将基类的值分配给派生类的对象,这会导致问题。

来自 this answer我了解到基类的赋值运算符总是被派生类的隐式定义的赋值运算符隐藏。所以对于简单变量的赋值,不正确的类型会导致编译器错误。但是,如果赋值是通过引用发生的,则情况并非如此:

class A { public: int a; };
class B : public A { public: int b; };
int main() {
  A a; a.a = 1;
  B b; b.a = 2; b.b = 3;
  // b = a; // good: won't compile
  A& c = b;
  c = a; // bad: inconcistent assignment
  return b.a*10 + b.b; // returns 13
}

这种形式的赋值可能会导致对象状态不一致,但是没有编译器警告,乍一看代码对我来说没有恶意。

是否有任何既定的惯用法来检测此类问题?

我想我只能寄希望于运行时检测,如果我发现这样的无效赋值则抛出异常。刚才我能想到的最好的方法是在基类中自定义赋值运算符,它使用运行时类型信息来确保this。实际上是指向 base 实例的指针,而不是指向派生类的指针,然后进行逐个成员的手动复制。这听起来像是很多开销,并且严重影响代码的可读性。有没有更简单的?

编辑:由于某些方法的适用性似乎取决于我想做什么,这里有一些细节。

我有两个数学概念,比如 ringfield .每个字段都是一个环,但反之则不然。每个都有几个实现,它们共享公共(public)基类,即 AbstractRingAbstractField , 后者源自前者。现在我尝试基于 std::shared_ptr 实现易于编写的引用语义.所以我的 Ring类包含一个 std::shared_ptr<AbstractRing>持有它的实现,以及一堆转发给它的方法。我想写Field作为继承自 Ring所以我不必重复那些方法。特定于字段的方法将简单地将指针转换为 AbstractField ,并且我想静态地执行该转换。我可以确保指针实际上是一个 AbstractField在 build 中,但我担心有人会分配 RingRing&这实际上是一个 Field ,从而打破了我假设的关于包含的共享指针的不变性。

最佳答案

由于在编译时无法检测到向下转换类型引用的赋值,因此我建议采用动态解决方案。这是一个不寻常的案例,我通常会反对这种做法,但可能需要使用虚拟赋值运算符。

class Ring {
    virtual Ring& operator = ( const Ring& ring ) {
         /* Do ring assignment stuff. */
         return *this;
    }
};

class Field {
    virtual Ring& operator = ( const Ring& ring ) {
        /* Trying to assign a Ring to a Field. */
        throw someTypeError();
    }

    virtual Field& operator = ( const Field& field ) {
        /* Allow assignment of complete fields. */
        return *this;
    }
};

这可能是最明智的做法。

另一种方法可能是为引用创建一个模板类,它可以跟踪这一点并简单地禁止使用基本指针 * 和引用 &。模板化的解决方案可能更难正确实现,但会允许静态类型检查来禁止向下转换。这是一个基本版本,至少对我而言,它使用 GCC 4.8 和 -std=c++11 标志(对于 static_assert)正确地给出了编译错误,“noDerivs(b)”是错误的来源。

#include <type_traits>

template<class T>
struct CompleteRef {
    T& ref;

    template<class S>
    CompleteRef( S& ref ) : ref( ref ) {
        static_assert( std::is_same<T,S>::value, "Downcasting not allowed" );
        }

    T& get() const { return ref; }
    };

class A { int a; };
class B : public A { int b; };

void noDerivs( CompleteRef<A> a_ref ) {
    A& a = a_ref.get();
}

int main() {
    A a;
    B b;
    noDerivs( a );
    noDerivs( b );
    return 0;
}

如果用户首先创建他自己的引用并将其作为参数传递,这个特定模板仍然可以被愚弄。最后,保护您的用户不做愚蠢的事情是一项无望的努力。有时,您所能做的就是发出合理的警告并提供详细的最佳实践文档。

关于c++ - 检测基类分配给指向派生类的引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25547066/

有关c++ - 检测基类分配给指向派生类的引用的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  4. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  5. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  6. ruby - 将全局 $stdout 重新分配给控制台 - ruby - 2

    我正在尝试将$stdout设置为临时写入一个文件,然后返回到一个文件。test.rb:old_stdout=$stdout$stdout.reopen("mytestfile.out",'w+')puts"thisgoesinmytestfile"$stdout=old_stdoutputs"thisshouldbeontheconsole"$stdout.reopen("mytestfile1.out",'w+')puts"thisgoesinmytestfile1:"$stdout=old_stdoutputs"thisshouldbebackontheconsole"这是输出。r

  7. ruby - Chef LW 资源属性默认值如何引用另一个属性? - 2

    我正在尝试将一个资源属性的默认值设置为另一个属性的值。我正在为我正在构建的tomcat说明书定义一个资源,其中包含以下定义。我想要可以独立设置的“名称”和“服务名称”属性。当未设置服务名称时,我希望它默认为为“名称”提供的任何内容。以下不符合我的预期:attribute:name,:kind_of=>String,:required=>true,:name_attribute=>trueattribute:service_name,:kind_of=>String,:default=>:name注意第二行末尾的“:default=>:name”。当我在Recipe的新block中引用我

  8. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  9. ruby - 在 Ruby 中,为什么 Array.new(size, object) 创建一个由对同一对象的多个引用组成的数组? - 2

    如thisanswer中所述,Array.new(size,object)创建一个数组,其中size引用相同的object。hash=Hash.newa=Array.new(2,hash)a[0]['cat']='feline'a#=>[{"cat"=>"feline"},{"cat"=>"feline"}]a[1]['cat']='Felix'a#=>[{"cat"=>"Felix"},{"cat"=>"Felix"}]为什么Ruby会这样做,而不是对object进行dup或clone? 最佳答案 因为那是thedocumenta

  10. ruby - 引用具有指定索引的枚举器值 - 2

    假设我有一个可枚举对象enum,现在我想获取第三个项目。我知道一种通用方法是转换成数组,然后使用索引访问,如:enum.to_a[2]但这种方式会创建一个临时数组,效率可能很低。现在我使用:enum.each_with_index{|v,i|breakvifi==2}但这非常丑陋和多余。执行此操作最有效的方法是什么? 最佳答案 你可以使用take剥离前三个元素,然后剥离last从take给你的数组中获取第三个元素:third=enum.take(3).last如果您根本不想生成任何数组,那么也许:#Ifenumisn'tanEnum

随机推荐