使 "typedef" 成为最终的(或模拟它)
Make a "typedef" be final (or simulate it)
是否可以将 type
的别名标记为 final
(即不能在派生的 class 中重新定义)?
#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,它仅用于功能。
有解决方法吗?
在某些情况下,它可以作为一个万无一失的编码器。
不,你不能。
基于特征的类型可以做到,但机制很丑。
可以通过基于 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++ 不执行的约束。可能,但不明智,除非你有充分的理由。
关于我们如何使用枚举或其他虚拟对象来有效地隐藏类型的切线答案。
/*
* 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);
}
这将产生一个错误,希望同时提及字符串和 DoNotUseStrings_DeadlockDanger,从而提供线索。
但它对类型的使用有限,因为虽然它阻止作者使用单词 "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 并免费获得隐藏行为,但许多人发现名称重用难以阅读。
我使用此技术的另一种方式是重构。我有一个愉快地使用成员值来控制其行为的方法,然后需要在必须从外部控制其中一个控制值的地方进行增强。为了实现这一点,原来的无参数方法变成了一个垫片,以成员为参数调用一个新方法。
但是如何确保新方法不会意外使用成员而不是参数?就个人而言,我会让论点掩盖成员,但我们再次受到他人理解的限制。
是否可以将 type
的别名标记为 final
(即不能在派生的 class 中重新定义)?
#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,它仅用于功能。
有解决方法吗?
在某些情况下,它可以作为一个万无一失的编码器。
不,你不能。
基于特征的类型可以做到,但机制很丑。
可以通过基于 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++ 不执行的约束。可能,但不明智,除非你有充分的理由。
关于我们如何使用枚举或其他虚拟对象来有效地隐藏类型的切线答案。
/*
* 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);
}
这将产生一个错误,希望同时提及字符串和 DoNotUseStrings_DeadlockDanger,从而提供线索。
但它对类型的使用有限,因为虽然它阻止作者使用单词 "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 并免费获得隐藏行为,但许多人发现名称重用难以阅读。
我使用此技术的另一种方式是重构。我有一个愉快地使用成员值来控制其行为的方法,然后需要在必须从外部控制其中一个控制值的地方进行增强。为了实现这一点,原来的无参数方法变成了一个垫片,以成员为参数调用一个新方法。
但是如何确保新方法不会意外使用成员而不是参数?就个人而言,我会让论点掩盖成员,但我们再次受到他人理解的限制。