In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly explains the "Visual C++ 2015 introduced updated C++ features", the content of the article is simple and clear, easy to learn and understand, now please follow the editor's ideas slowly in depth, together to study and learn "what are the updated C++ features introduced by Visual C++ 2015"?
Visual C++ 2015 is the result of the tremendous efforts made by C++ team to introduce modern C++ into the windows platform. In several distributions, VC++ has gradually added the features of the modern C++ language and libraries, which together create an absolutely amazing development environment for building generic windows App and components. Visual custom 2015 builds on the amazing advances introduced in earlier versions, providing a mature compiler that supports most of its 11 features and a subset of its 2015 subset. You may doubt the completeness of the compiler's support, but to be fair, I think he can support most of the important language features, and support for modern C++ will usher in a new world of windows library development. That's the point. As long as the compiler supports an efficient and elegant library development environment, developers can build great app and components.
Instead of showing you a boring list of new features or taking a quick look at its functions, I'll show you how some traditional complex code is quite enjoyable to write now. Of course, this is thanks to the mature Visual C++ compiler. I'm going to show you some of the nature of windows, which is actually very important in API now or in the future.
Ironically, C++ is modern enough for COM. Yes, I'm talking about the component object Model (COM), which has been the cornerstone of most Windows API for years. At the same time, it continues to be the cornerstone of the Windows runtime. Com is indisputably attached to the original design of C++, drawing on a lot of binary and semantic conventions from C++, but it is never elegant enough. Some parts of C + +, such as dynamic_cast, must be avoided in order to adopt portable solutions, which makes the development and implementation of C++ more challenging. In recent years, many solutions have been provided for C++ developers to make COM more portable. C + + / CX language extension may be the ambition of the Visual C++ team so far. Ironically, these efforts to improve C++ support have abandoned C++/CX and made language expansion redundant.
To prove this, I will show you how to fully implement the IUnknown and IInspectable interfaces with Modern C++. There is nothing modern or attractive about these two interfaces. IUnknown continues to be a centralized abstraction of great API, such as DirectX. These interfaces-- IInspectable-- inherit from IUnknown-- 's central location at the Windows runtime. I'll show you how to implement them, interface tables or other macros without any language extensions-- all you need is an efficient and elegant Category dialog that contains a lot of type information, and you can let compilers and developers have excellent man-machine conversations about how to create what you need.
The main problem is how to list the interfaces that COM or Windows Runtime classes need to implement and make them easy for developers to use and compiler to access. For example, list all available types so that the compiler can query or even enumerate the corresponding interfaces. If you can achieve this function, you may be able to let the compiler generate code for IUnknown QueryInterface or even IInspectable GetIids methods. These two methods are the crux of the problem. Traditionally, the only solution involves language extensions (language extensions), nefarious macro definitions, and a pile of hard-to-maintain code.
The implementation of both methods uses the interface that the class needs to implement. Variable parameter template (variadic template) is *:
Template class _ _ declspec (novtable) Implements: public Interfaces. {}
The _ _ declspec (novtable) extension property prevents constructors and destructors from initializing the vfptr of an abstract class, which usually means reducing a lot of code. The implementation class template includes a template parameter package, which makes it a variable template. A parameter package is a template parameter that accepts any number of template parameter variables. But in this case, the template parameters I describe will only be queried at compile time. The interface will not appear in the function's argument list.
One use of these parameters is already obvious. The parameter package is extended to become the parameter list of the common base class. Of course, I still have a responsibility to * implement these virtual functions, but at this point I will describe a concrete class that implements any number of interfaces:
Class Hen: public Implements {}
Because the parameter package expands to a list of specified base classes, all of it is equivalent to the following code that I might write:
Class Hen: public IHen, public IHen2 {}
The beauty of structuring the implementation class template in this way is that I can now write various sample implementation code in the implementation class template, while developers of the Hen class can use this unabrupt abstraction while ignoring a lot of implicit details.
So far, everything is fine. Now, I will consider the implementation of IUnknown. I should be able to implement it completely in the implementation class template and provide the type information that the compiler now has. IUnknown provides two tools that are very important to the COM class, just as oxygen and water are to humans. * one that might be simpler is reference counting, which is how COM objects track their lifecycles. Com specifies an intrusive reference count with the help of each object. Count how many external references exist to manage your lifecycle. This is contrary to the reference count of smart pointers, such as C++ 11's shared_ptr class, where smart pointer objects are unaware of their sharing relationships. You may argue about the advantages and disadvantages of these two approaches. But, in fact, COM's approach is usually more efficient, which is how COM works, and you have to deal with it. If there's nothing else, you'll probably agree that wrapping a COM interface in shared_ptr would be extremely unfriendly!
I'll start with only runtime overhead, which is introduced by implementing the class template:
Protected: unsigned long m_references = 1; Implements () noexcept = default; virtual ~ Implements () noexcept {}
The default constructor is not the real overhead, it simply ensures that the final constructor-which initializes the reference count-is protected rather than public. Both reference counting and fictional functions are protected. Let derived classes access reference counting in order to allow more complex class combinations. Most classes can simply ignore it, but it's important to note that I'm initializing the reference count to 1. 0. This is in sharp contrast to the usual recommendation to initialize the reference count to 0, because references are not processed at this time. This method is very popular in ATL and is obviously influenced by Don Box's COM essence theory, but it is very problematic, and the study of ATL source code can be used as evidence. It starts with the assumption that the ownership of the reference will be immediately acquired by the caller or attached to a smart pointer that provides less error construction handling.
Virtual destructors provide great convenience by allowing implementation class templates to implement reference counting rather than forcing the implementation class itself to provide implementation. Another option is to use the strange recursive template pattern (Curiously Recurring Template Pattern) to avoid using virtual functions. I usually choose this method, but it adds a little complexity to the abstraction, and because the COM class itself has a vtable, there is no reason to avoid using virtual functions. With these basic types, it is very easy to implement AddRef and Release in the implementation class template. First, the AddRef method can simply use InterlockedIncrement to increase the reference count:
Virtual unsigned long _ _ stdcall AddRef () noexcept override {return InterlockedIncrement (& m_references);}
This is self-evident. Don't come up with some complicated methods, use C++ 's addition and subtraction operators to conditionally replace InterlockedIncrement and InterlockedDecrement functions. ATL tries to do this with great complexity. If you think about efficiency, you'd rather pay more attention to avoid fallacies in calling AddRef and Release. Similarly, Modern C++ has added support for move semantics and the ability to transfer ownership of references. For now, the Release method is only slightly more complex:
Virtual unsigned long _ _ stdcall Release () noexcept override {unsigned long const remaining = InterlockedDecrement (& m_references); if (0 = = remaining) {delete this;} return remaining;}
After the reference count is reduced, the result is assigned to a temporary variable. This is important because the result needs to be returned. But if the object is destroyed, the member variable referencing the object is illegal. Assuming that there are no other unhandled references, the object is deleted through the aforementioned virtual destructor. This is the conclusion of reference counting, and the implementation of the class Hen is still as simple as before:
Class Hen: public Implements {}
Now, it's time to imagine the wonderful world of QueryInterface. Implementing the IUnknown method is a very important practice. I have implemented it extensively in my Pluralsight course. You can read about the wonderful and incredible ways to implement your own IUnknown in the book (Addison-Wesley Professional,1998) written by Don Box. It should be noted that although this is an excellent book about COM, it is based on Clipper 98 and does not show any of the characteristics of modern C++. To save time, I assume that you are already familiar with the implementation of QueryInterface and focus on how to implement it with modern C++. Here is the virtual function itself:
Virtual HRESULT _ stdcall QueryInterface (GUID const & id, void * * object) noexcept override {}
Given that a GUID is used to identify a particular interface, the QueryInterface should decide whether an object implements the desired interface. If implemented, it must reduce the reference count of the object and return the required interface pointer through external parameters. If it is not implemented, it must return a null pointer. So I'm going to start with a rough outline:
* object = / / Find interface somehow if (nullptr = = * object) {return Elastic NOINTERFACES;} static_cast (* object)-> AddRef (); return S_OK
QueryInterface will first try to find the interface you need. Returns an E_NOINTERFACE error code if the interface is not supported. Notice how I handle situations where interface pointers are not supported as required. You should think of the QueryInterface interface as a binary operation. It either succeeds in finding the required interface or fails to find it. Don't try to be creative, just respond according to conditions. Although the COM specification has some limitations, most consumers will simply assume that the interface is not supported, regardless of what error code you return. Any error in your implementation will undoubtedly lead you to the abyss of debugging. QueryInterface is very basic and cannot be treated indiscriminately. * AddRef is called again by the API pointer to support a few but allowed class composition scenarios. These are not explicitly supported by implementation class templates, but I prefer to set an example here. It is important to remember that reference counting operations are interface-oriented, not object-oriented. You can't simply call AddRef or Release on any interface that belongs to an object. You must rely on COM rules to manage objects, or you will risk introducing illegal code that crashes in incredible ways.
But how do I know if the requested GUID represents the interface that the class wants to implement? I need to go back to the place where I implement the type information collected by the class template, where the type information is collected through its template parameter package. Remember, my goal is to allow the compiler to implement it for me. I hope the final code will be as efficient or even better as my handwritten code. I will query through the collection of variable function templates, which itself includes the template parameter package. I'll start with the BaseQueryInterface function template:
Virtual HRESULT _ stdcall QueryInterface (GUID const & id, void * * object) noexcept override {* object = BaseQueryInterface (id)
BaseQueryInterface is essentially the modern C++ version of IUnknown QueryInterface. It returns the interface pointer directly instead of the HRESULT type. A null pointer indicates a failure. It takes a single function argument, and the GUID identifies the interface to look for. More importantly, I have extended the class template parameter package to the full schema so that the BaseQueryInterface function can begin the process of enumerating interfaces. At first you might think that because BaseQueryInterface is a member function that implements the class template, it can simply and directly access the linked list of interfaces, but I need to allow this function to strip the * interfaces in the linked list, like this:
Template void * BaseQueryInterface (GUID const & id) noexcept {}
In this way, the BaseQueryInterface function recognizes * interfaces and leaves room for subsequent searches. See, COM has a number of special rules to support QueryInterface implementation or at least accept object recognition. In particular, the request IUnknown must always return the exact same pointer before the client can determine whether the pointers of the two interfaces come from the same object. So the best thing about the BaseQueryInterface function is that it implements these assumptions. Therefore, you can start with the comparison of GUID requests that represent the template parameters of * interfaces that the class wants to implement. If it does not match, I will check to see if IUnknown has started to request:
If (id = = _ uuidof (First) | | id = = _ _ uuidof (:: IUnknown)) {return static_cast (this);}
Assuming there is a match, I return pointers to * interfaces directly and accurately. Static_cast ensures that multiple IUnknown-based interfaces of the compiler will not cause ambiguity. Cast simply calibrates the pointer so that the vtable of the class can find the correct pointer position, because all vtable interfaces start with the three methods of IUnknown, which is very logical.
I might as well add optional support for IInspectable queries as well. IInspectable is quite perverted, in a sense, it is a Windows runtime interface, because each Windows runtime expects programming languages (such as C # and JavaScript) to come directly from IInspectable, not just the IUnknown interface. In contrast to the way C++ works and the traditional definition of COM, it is unfortunate to implement objects and interfaces in a way that adapts to the common language runtime. More unfortunately, the impact on performance when objects are combined, which I will discuss below. As for QueryInterface, I just need to make sure that IInspectable can be queried, and it should be an implementation of a Windows runtime class, not a simple typical COM class. Although the explicit COM rules for IUnknown do not apply to IInspectable, I can simply treat the latter in the same way. But these two challenges. First, you need to know if there are any interface implementations derived from IInspectable. Second, you need to know the type of interface so that you can correctly return an adjusted interface pointer without ambiguity. Assuming that the * APIs in the list are all based on IInspectable, you can only update the BaseQueryInterface as follows:
If (id = = _ uuidof (First) | | id = = _ _ uuidof (:: IUnknown) | | (std::is_base_of::value & & id = = _ _ uuidof (:: IInspectable) {return static_cast (this);}
Note that I am using the is_base_of feature in C++ 11 to determine that the * * template parameters are a derivative of IInspectable. In case the implementation of a typical COM class does not support the Windows runtime, you can ensure that subsequent comparisons are excluded by the compiler. This allows me to seamlessly support the Windows runtime and classic COM classes without increasing the statement complexity of the component developer and without any unnecessary runtime overhead. However, if you happen to encounter that the first one listed is not the IInspectable interface, there will be hidden dangers of Bug that are not easy to detect. All you need to do is to scan the entire list of interfaces in some way instead of is_base_of:
Template constexpr bool IsInspectable () noexcept {return std::is_base_of::value | | IsInspectable ();}
IsInspectable is also based on the is_base_of feature, but is currently suitable for matching interfaces. If no IInspectable-based interface is found, it terminates:
Template constexpr bool IsInspectable () noexcept {return false;}
I will disable the bizarre default parameters. Assuming that IsInspectable returns true, I need to find * IInspectable-based interfaces:
Template void * FindInspectable () noexcept {return nullptr;} template void * FindInspectable () noexcept {/ / Find somehow}
Use the is_base_of feature again, but this time return a real matching interface pointer:
# pragma warning (push) # pragma warning (disable:4127) / / conditional expression is constant if (std::is_base_of::value) {return static_cast (this);} # pragma warning (pop) return FindInspectable ()
At this point, BaseQueryInterface can use IsInspectable and FindInspectable to support querying IInspectable:
If (IsInspectable () & & id = _ _ uuidof (:: IInspectable)) {return FindInspectable ();}
Then specify the concrete Hen class:
Class Hen: public Implements {}
Implementing a template for a class ensures that the compiler generates more efficient code, whether IHen or Hen2 comes from IInspectable or IIUnknown (or other interfaces). Now, I can * implement the recursive portion of QueryInterface, as well as any additional interfaces, such as IHen2 in the example above. BaseQueryInterface ends by calling the FindInterface function template:
Template void * BaseQueryInterface (GUID const & id) noexcept {if (id = = _ _ uuidof (First) | | id = = _ _ uuidof (:: IUnknown)) {return static_cast (this);} if (IsInspectable () & & id = = _ uuidof (:: IInspectable)) {return FindInspectable ();} return FindInterface (id);}
Notice that I call this FindInterface function template, which is roughly the same as the BaseQueryInterface I originally called, and in this case, I pass it the rest of the interface. I deliberately expanded the parameter package again so that it could identify the * interface in the rest of the list. But it will prompt a malfunction. Because the template parameter package is not extended with function arguments, this can become tricky and the programming language can't write what I want. More often, this "recursive" FindInterface variable template is exactly what you want:
Template void * FindInterface (GUID const & id) noexcept {if (id = = _ uuidof (First)) {return static_cast (this);} return FindInterface (id);}
It separates from the rest of the template parameters and returns the adjusted interface pointer if there is a match. In addition, it also calls itself until the list is finished. When I mention compile-time recursion in general, it is important to note that this function template, and other similar examples of implementing class templates, are technically recursive, not at compile time. An instance of each function template calls an instance of a different function template. For example, FindInterface calls FindInterface and FindInterface calls FindInterface. In order to make it recursive, FindInterface does not need to call FindInterface.
Still, keep in mind that such "recursion" occurs at compile time, just like your own handwritten if statements. But now, I'm in trouble. How does this sequence end? Of course, when the template parameter list is empty. The problem is that C++ has defined the meaning of an empty template parameter list:
Template void * FindInterface (GUID const &) noexcept {return nullptr;}
This is almost true, but the compiler will prompt you that function templates cannot be used in this specialization. At the same time, if I do not provide a terminating function, the compiler will not be able to compile the final call when the parameter package is empty. This is not the case of function overloading, because the argument list is still the same. Fortunately, the solution is very simple. I can prevent the terminating function from looking like a specialization by providing an unnamed default parameter:
Template void * FindInterface (GUID const &) noexcept {return nullptr;}
The compiler is happy with this, and if you request an unsupported interface, the terminating function simply returns a null pointer, while the virtual function QueryInterface returns an E_NOINTERFACE error code. For IUnknown, this is very thoughtful. If all you care about is the classic COM, you can park there safely, because that's all you need. This can be done repeatedly, and the compiler will optimize the implementation of QueryInterface, using a variety of "recursive" function calls and constant expressions, and the code is at least as good as you write by hand. For IInspectable, the same approach can be achieved.
For Windows runtime classes, implementing IInspectable adds additional complexity. This interface is not the same basic interface as IUnknown, it provides a collection of uncertain tools, while IUnknown provides absolutely basic functions. I'll leave a discussion about this for future articles and focus on efficient and modern C++ implementations that support any Windows runtime class. First, I'll avoid the GetRuntimeClassName and GetTrustLevel virtual functions. The implementation of these two methods is relatively trivial, and since they are rarely used, their implementation can be simply prevaricated. The GetRunTimeClassName method, which needs to return a string of the full name of the runtime class that this object represents. I'll leave this to the class itself and decide whether to do it or not. The implementation class template can simply return E_NOTIMPL to indicate that this method is not implemented:
HRESULT _ stdcall GetRuntimeClassName (HSTRING * name) noexcept {* name = nullptr; return updated NOTIMPLL;}
Similarly, the GetTrustLevel method simply returns constants of enumerated types:
HRESULT _ stdcall GetTrustLevel (TrustLevel * trustLevel) noexcept {* trustLevel = BaseTrust; return showing OK;}
It is important to note that I did not show the mark that these IInspectable methods are virtual functions. Avoid declaring virtual functions in order for the compiler to eliminate these functions, and the COM class does not need to actually implement any IInspectable interfaces. Now, I turn my attention to the IInspectable GetIids method. This is more prone to errors than QueryInterface. Although its implementation is not so strict, an efficient compiler-generated implementation is also desirable. GetIids returns a dynamically allocated GUID array. Each GUID represents an interface to be implemented by an object. At first you might think that this is just a simple declaration supported by the object through QueryInterface, but that is only ostensibly correct. The GetIids method may retain some interfaces without exposing them. In any case, I will start with the basic definition:
HRESULT _ _ stdcall GetIids (unsigned long * count, GUID * * array) noexcept {* count = 0; * array = nullptr
* parameters point to the variable provided by the caller, where the GetIids method must be set to the number of interfaces in the resulting array. The second parameter points to an GUID array and also indicates how the implementation here returns the dynamically allocated array to the caller. Here, I cleared both parameters, just to be safe. Now I need to decide how many interfaces this class implements. What I want to say is that using the sizeof operator, it can determine the size of this parameter package, as follows:
Unsigned const size = sizeof... (Interfaces)
This is quite convenient, and at the same time, the compiler reports the number of template parameters to be displayed after the parameter package is expanded. This is also a valid constant expression that produces a value at compile time. As I mentioned a little earlier, the reason this is not possible is that it is quite common for GetIids implementations to retain interfaces that they do not want to share with others. These interfaces are called implicit interfaces. Anyone can query them through QueryInterface, but GetIids will not tell you that these interfaces are available. In this way, I need to provide a compile-time alternative to the variable sizeof operator that excludes the implicit interface. At the same time, I need to provide some way to declare and identify these implicit interfaces. I start with the latter. For component developers, I want to make implementing classes as simple as possible, which requires a less obscure skill. I simply provide a Cloaked class template to "modify" any implicit interface:
Template struct Cloaked: Interface {}
Then, I decided to implement a special "IHenNative" interface on the class Hen, which is not known to all users:
Class Hen: public Implements {}
Because the Cloaked class template inherits from its template parameters, the existing QueryInterface implementation can continue to work seamlessly. I've added a little extra compile-time type information, and now I can query them. From this, I will define an IsCloaked type so that I can easily query any interface to determine whether it is hidden:
Template struct IsCloaked: std::false_type {}; template struct IsCloaked: std::true_type {}
Now I can use a recursive variable function template to calculate the number of unhidden interfaces:
Template constexpr unsigned CounInterfaces () noexcept {return! IsCloaked::value + CounInterfaces ();}
Of course, I need a termination function that simply returns 0:
Template constexpr unsigned CounInterfaces () noexcept {return 0;}
The powerful ability to perform arithmetic calculations at compile time with modern C++ is stunning and amazingly simple. Now I continue to refine the implementation of GetIids through the number of requests:
Unsigned const localCount = CounInterfaces ()
One thing that is not satisfactory is that the compiler's support for constant expressions is not very mature. Although there is no doubt that this is a constant expression, the compiler does not view constexpr member functions that way. Ideally, I can identify the CountInterfaces function template as constexpr, and the resulting expression will be a constant expression, but the compiler won't think so. On the other hand, there is no doubt that the compiler will optimize this code. Now, whatever the reason, if CountInterfaces does not find the implicit interface, GetIids will simply return success because the resulting array will be empty:
If (0 = = localCount) {return showing OK;}
Again, this is a valid constant expression, and the compiler generates code without any conditions. In other words, if there are no unhidden interfaces, the rest of the code will simply be removed from the implementation. Otherwise, the implementation of the code forces the use of a traditional COM allocator to allocate a reasonable size GUID array:
GUID * localArray = static_cast (CoTaskMemAlloc (sizeof (GUID) * localCount))
Of course, this may fail, in which case I simply return the appropriate HRESULT value:
If (nullptr = = localArray) {return Elastic OUTOFMERY;}
At this point, GetIids prepares an array to hold the GUID. As you might expect, I need to enumerate the interfaces once, and then copy the GUID of each unhidden interface into this array. I will use a set of function templates, just like I used before:
Template void CopyInterfaces (GUID *) noexcept {} template void CopyInterfaces (GUID * ids) noexcept {}
This variable template (the second function) can simply use the IsCloaked type to determine whether to copy the GUID of the interface identified by the First template parameter before adding the pointer. In this way, you can traverse the array without recording how many elements it contains or where it will be written to the array. I also banned warnings about constant expressions:
# pragma warning (push) # pragma warning (disable:4127) / / Conditional expression is constant if (! IsCloaked::value) {* ids++ = _ _ uuidof (First);} # pragma warning (pop) CopyInterfaces (ids)
As you can see, the "recursive" call to CopyInterfaces in * * uses the value of the pointer that may be increased. I almost finished (the whole implementation process). Before returning to the caller, the implementation of GetIids can populate the array by calling CopyInterfaces:
CopyInterfaces (localArray); * count = localCount; * array = localArray; return Skek;}
For the Hen class, all the actions the compiler does on it are transparent (it is completely unaware of these operations):
Class Hen: public Implements {}; Thank you for reading, the above is the content of "what are the updated C++ features introduced by Visual C++ 2015?" after the study of this article, I believe you have a deeper understanding of what the updated C++ features introduced by Visual C++ 2015 have, and the specific use still needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.