Effective C++: Item 43 -- Know how to access names in templatized base classes.

Problem: Unable to access names in templatized base class

In example:

class CompanyA { 
public:
	...
	void sendCleartext(const std::string& msg); 
	void sendEncrypted(const std::string& msg); 
	...
};

class CompanyB { 
public:
	...
	void sendCleartext(const std::string& msg); 
	void sendEncrypted(const std::string& msg); 
	...
};
...		// classes for other companies

class MsgInfo { ... };	// class for holding information 
						// used to create a message
template<typename Company> 
class MsgSender {
public:
	... // ctors, dtor, etc.
	void sendClear(const MsgInfo& info) {	
		std::string msg; create msg from info;
		Company c;
		c.sendCleartext(msg); 
	}
	void sendSecret(const MsgInfo& info) // similar to sendClear, 
	{ ... }								//except  calls c.sendEncrypted
};

// Derived class
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> { 
public:
	...
	void sendClearMsg(const MsgInfo& info) {
		write "before sending" info to the log;
		sendClear(info);// call base class function;
						// this code will not compile!
		write "after sending" info to the log; 
	}
	... 
};

Such compilers will complain that sendClear doesn’t exist.
Reason:
When compilers encounter the definition for the class template LoggingMsgSender, they don’t know what class it inherits from. Because Company is a template parameter that won’t be known until later (when LoggingMsgSender is instantiated). Without knowing what Company is, there’s no way to know what the class MsgSender<Company> looks like.

Total Template Specialization: provide specialized template

Now suppose we have a new class CompanyZ that insists on encrypted communications:

class CompanyZ {  // this class offers no
public: 		  // sendCleartext function
	...
	void sendEncrypted(const std::string& msg); 
	...
};

The general MsgSender template is inappropriate for CompanyZ, because that template offers a sendClear function that makes no sense for CompanyZ objects.
Solution:

template<>						// a total specialization of
class MsgSender<CompanyZ> { 	// MsgSender; the same as the
public:							// general template, except
								// sendClear is omitted	...
	void sendSecret(const MsgInfo& info) {...}
};

Note the template <> syntax at the beginning of this class definition. It signifies that this is neither a template nor a standalone class. Rather, it’s a specialized version of the MsgSender template to be used when the template argument is CompanyZ. This is known as a total template specialization: the template MsgSender is specialized for the type CompanyZ, and the specialization is total – once the type parameter has been defined to be CompanyZ, no other aspect of the template’s parameters can vary.

If we recall the previous LoggingMsgSender class, it makes no sense when the base class is MsgSender<CompanyZ>, because that class offers no sendClear function. That’s why C++ rejects the call: it recognizes that base class templates may be specialized and that such specializations may not offer the same interface as the general template.

Solution : disable C++’s “don’t look in templatized base classes” behavior.

  1. preface calls to base class functions with this->:
    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company> { 
    public:
    	...
    	void sendClearMsg(const MsgInfo& info) {
    		write "before sending" info to the log; 
    		this->sendClear(info);	// okay, assumes that
    								// sendClear will be inherited
    		write "after sending" info to the log; 
    	}
    	... 
    };
    
  2. employ a using declaration, a solution that should strike you as familiar if you’ve read Item 33.
    template<typename Company>
    class LoggingMsgSender: public MsgSender<Company> { 
    public:
    	using MsgSender<Company>::sendClear;// tell compilers to assume 
    	...									// that sendClear is in the 
    										// base class
    	void sendClearMsg(const MsgInfo& info) {
    		... 
    		sendClear(info); // okay, assumes that
    						// sendClear will be inherited
    		...
    	}
    	... 
    };
    
  3. explicitly specify that the function being called is in the base class:
    	template<typename Company>
    	class LoggingMsgSender: public MsgSender<Company> { 
    	public:
    		void sendClearMsg(const MsgInfo& info) {
    			... 
    			MsgSender<Company>::sendClear(info);// okay, assumes that
    												// sendClear will be 
    												// inherited
    			...
    		}
    		... 
    	};
    
    This is generally the least desirable way to solve the problem, because if the function being called is virtual, explicit qualification turns off the virtual binding behavior.

Note

From a name visibility point of view, each of these approaches does the same thing: it promises compilers that any subsequent specializations of the base class template will support the interface offered by the general template.
If the promise turns out to be unfounded, there will be problem.

LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
... // put info in msgData 
zMsgSender.sendClearMsg(msgData); // error! won’t compile

the call to sendClearMsg won’t compile, because at this point, compilers know that the base class is the template specialization MsgSender<CompanyZ>

Things to Remember

  • In derived class templates, refer to names in base class templates via a “this->” prefix, via using declarations, or via an explicit base class qualification.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值