代码之家  ›  专栏  ›  技术社区  ›  cppBeginner

将“typedef”设为最终(或模拟)

  •  0
  • cppBeginner  · 技术社区  · 7 年前

    是否可以标记 type final (即不能在派生类中重新定义)?

    #include <iostream>
    class B{
        public: using type=std::string;
    };
    class D : public B{
        public: using type=int;     //<--- [1] I want a compile error here.
    };
    int main(){
        typename D::type str="abc"; //<--- [2] This line is actually correct.
    }
    

    根据 http://en.cppreference.com/w/cpp/language/final ,仅用于功能。
    有解决方法吗?

    在某些情况下,它可以作为程序员的傻瓜证明。

    2 回复  |  直到 7 年前
        1
  •  4
  •   Yakk - Adam Nevraumont    7 年前

    不,你不能。

    基于特征的类型可以做到这一点,但机器是丑陋的。

    可以通过基于adl的标记功能图定义分布式类型图。

    template<class T>struct tag_t{constexpr tag_t(){} using type=T;};
    template<class T>constexpr tag_t<T> tag{};
    
    namespace trait {
      template<class T>
      constexpr void type_tag( tag_t<T> ){}
      template<class T>
      using type=typename decltype( type_tag( tag<T> ) )::type;
    }
    
    // non-final alias
    struct A{
      friend constexpr tag_t<int> type_tag(tag_t<A>){return {};}
    };
    // non-final alias
    struct A{
      friend constexpr tag_t<char> type_tag(tag_t<A>){return {};}
    };
    // final alias
    struct B{
      template<class T, std::enable_if_t< std::is_base_of<B,T>{}, bool> =true>
      friend constexpr tag_t<std::string> type_tag(tag_t<T>){return {};}
    };
    

    现在覆盖 A type_tag trait::type<> 但是如果你用同样的方法 B 你会得到一个很长很难理解的错误。

    这是一个糟糕的计划。

    元类可能也会让你做这样的事情。

    一般来说,这两种语言都需要编写一个新的C++子语言来执行C++没有执行的约束。可能,但不明智,除非你有非常好的理由。

        2
  •  1
  •   Gem Taylor    7 年前

    关于如何使用枚举或其他假人有效隐藏类型的切题答案。

    /*
     * Imagine everybody uses managed strings in our project throughout,
     * so everyone expects to be able to declare and print strings everywhere,
     * especially for debugging...
     */
    typedef OurInternalMangedStrings string;
    /***/
    
    void InternalStringManager::ReallyDoingNastyInternalStuff()
    {
        // Within this code casually using managed strings 
        // to format error messages, etc, 
        // would be fatal as it will cause nasty recursion.
        enum DoNotUseStrings_DeadlockDanger { string, OurInternalMangedStrings };
        printError(string ("I had an error here and I don't know why - code ") << errCode);
    
    }
    

    这将产生一个错误,希望同时提到string和donotusestings\u死锁危险,并给出线索。

    但它对类型的使用有限,因为虽然它阻止作者使用单词“string”,但它不会阻止代码自动执行转换,或使用已经存在的该类型的对象,例如,如果构造函数不显式,则以下内容将不加注释地传递:

    printError("I had an error here and I don't know why at all!");
    

    对于数据值,我发现它更有用:

    void MyManager::Setup()
    {
        {    SomeFeature newPimple = new Somefeature;
             enum DoNotUseMember {someFeature};
    
             /** set up newPimple using other member data and parameters 
                 when it is ready I will assign it to the member variable "someFeature"
              **/
             /** any accidental use of a someFeature member will produce error message **/
             // Ready to install the new pimpl as the visible feature
             MUTEX_RAII(access_feature); // ... Or whatever might be needed
             /* can still access someFeature by being explicit */
             delete this->someFeature;
             this->someFeature = newPimpl;
        }
        /** other setup code that uses the new feature **/
    }
    

    就我个人而言,我会将新实例称为someFeature,并免费获得隐藏行为,但许多人发现名称重用很难理解。

    我使用这种技术的另一种方法是重构。我有一种方法,它愉快地使用成员值来控制其行为,然后需要进行增强,其中一个控制值必须由外部控制。为了实现这一点,原始的无参数方法成为一个填充程序,调用一个以成员为参数的新方法。

    但是,如何确保新方法不会意外地使用成员而不是参数?就我个人而言,我会让这个论点掩盖了这个成员,但我们再次受到其他人理解的限制。