5. सरणियों का उपयोग करते समय सामान्य नुकसान।
5.1 ख़तरा: टाइपिंग-असुरक्षित लिंकिंग पर भरोसा करना।
ठीक है, आपको बताया गया है, या अपने आप को पता चला है, कि ग्लोबल्स (नामस्थान गुंजाइश चर जो अनुवाद इकाई के बाहर तक पहुँचा जा सकता है) ईविल ™ हैं। लेकिन क्या आप जानते हैं कि वास्तव में ईविल ™ वे कैसे हैं? नीचे दिए गए कार्यक्रम पर विचार करें, जिसमें दो फाइलें शामिल हैं [main.cpp] और [numbers.cpp]:
// [main.cpp]
#include <iostream>
extern int* numbers;
int main()
{
using namespace std;
for( int i = 0; i < 42; ++i )
{
cout << (i > 0? ", " : "") << numbers[i];
}
cout << endl;
}
// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
विंडोज 7 में यह MinGW g ++ 4.4.1 और Visual C ++ 10.0 दोनों के साथ ठीक से संकलित और लिंक करता है।
चूंकि प्रकार मेल नहीं खाते हैं, इसलिए जब आप इसे चलाते हैं तो प्रोग्राम क्रैश हो जाता है।
इन-फॉर्मल एक्सप्लेनेशन: इस प्रोग्राम में अनफाइंड बिहेवियर (UB) है, और इसके बजाय दुर्घटनाग्रस्त होने के कारण यह बस लटका सकता है, या शायद कुछ भी नहीं कर सकता है, या यह संयुक्त राज्य अमेरिका, रूस, भारत के राष्ट्रपतियों को धमकी भरे ई-मेल भेज सकता है चीन और स्विटजरलैंड, और नाक डेमन अपने नाक से बाहर उड़ते हैं।
इन-प्रैक्टिस एक्सप्लेनेशन: main.cpp
एरे में एक पॉइंटर के रूप में व्यवहार किया जाता है, जिसे एरे के समान पते पर रखा जाता है। 32-बिट निष्पादन योग्य के लिए इसका मतलब है कि int
सरणी में पहला
मान, एक पॉइंटर के रूप में माना जाता है। यानी, में चर होता है, या प्रकट होता है को रोकने के लिए, । यह प्रोग्राम को पता स्थान के बहुत नीचे मेमोरी तक पहुंचने का कारण बनता है, जो पारंपरिक रूप से आरक्षित और जाल-कारण है। परिणाम: आप एक दुर्घटना हो।main.cpp
numbers
(int*)1
इस त्रुटि का निदान नहीं करने के लिए कंपाइलर पूरी तरह से अपने अधिकारों के भीतर हैं, क्योंकि C ++ 11 .53.5 / 10 कहता है, घोषणाओं के लिए संगत प्रकारों की आवश्यकता के बारे में,
[N3290 .53.5 / 10]
प्रकार की पहचान पर इस नियम के उल्लंघन के लिए निदान की आवश्यकता नहीं होती है।
एक ही पैराग्राफ विवरण की अनुमति देता है:
... एक सरणी ऑब्जेक्ट के लिए घोषणाएं सरणी प्रकार निर्दिष्ट कर सकती हैं जो प्रमुख सरणी बाध्य (8.3.4) की उपस्थिति या अनुपस्थिति से भिन्न होती हैं।
इस अनुमति भिन्नता में एक अनुवाद इकाई में एक सरणी के रूप में एक नाम घोषित करने और किसी अन्य अनुवाद इकाई में एक सूचक के रूप में शामिल नहीं है।
5.2 नुकसान: समय से पहले अनुकूलन ( memset
और दोस्त) करना।
अभी तक नहीं लिखा
5.3 ख़तरा: तत्वों की संख्या प्राप्त करने के लिए C मुहावरे का उपयोग करना।
गहन सी अनुभव के साथ यह लिखना स्वाभाविक है ...
#define N_ITEMS( array ) (sizeof( array )/sizeof( array[0] ))
चूंकि array
पहले तत्व को इंगित करने के लिए एक डिक्रेटर जहां आवश्यक हो, अभिव्यक्ति sizeof(a)/sizeof(a[0])
को भी लिखा जा सकता है
sizeof(a)/sizeof(*a)
। इसका मतलब समान है, और कोई फर्क नहीं पड़ता कि यह कैसे लिखा जाता है यह सरणी के संख्या तत्वों को खोजने के लिए सी मुहावरा है ।
मुख्य नुकसान: सी मुहावरे प्रकार नहीं हैं। उदाहरण के लिए, कोड ...
#include <stdio.h>
#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))
void display( int const a[7] )
{
int const n = N_ITEMS( a ); // Oops.
printf( "%d elements.\n", n );
}
int main()
{
int const moohaha[] = {1, 2, 3, 4, 5, 6, 7};
printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
display( moohaha );
}
एक सूचक को पास करता है N_ITEMS
, और इसलिए सबसे अधिक संभावना एक गलत परिणाम पैदा करता है। विंडोज 7 में एक 32-बिट निष्पादन योग्य के रूप में संकलित…
7 तत्व, कॉलिंग डिस्प्ले ...
1 तत्व।
- संकलक
int const a[7]
बस फिर से लिखता है int const a[]
।
- संकलक को फिर
int const a[]
से लिखता है int const* a
।
N_ITEMS
इसलिए एक पॉइंटर के साथ लगाया जाता है।
- एक 32-बिट निष्पादन योग्य
sizeof(array)
(एक पॉइंटर का आकार) के लिए फिर 4 है।
sizeof(*array)
के बराबर है sizeof(int)
, जो एक 32-बिट निष्पादन योग्य के लिए भी 4 है।
रन समय में इस त्रुटि का पता लगाने के लिए आप क्या कर सकते हैं ...
#include <assert.h>
#include <typeinfo>
#define N_ITEMS( array ) ( \
assert(( \
"N_ITEMS requires an actual array as argument", \
typeid( array ) != typeid( &*array ) \
)), \
sizeof( array )/sizeof( *array ) \
)
7 तत्व, कॉलिंग डिस्प्ले ...
अभिकथन विफल: ("N_ITEMS को तर्क के रूप में वास्तविक सरणी की आवश्यकता होती है", टाइप (a)! = Typeid (& * a)), फ़ाइल runtime_detect ion.cpp, लाइन 16
इस एप्लिकेशन ने रनटाइम से असामान्य तरीके से इसे समाप्त करने का अनुरोध किया है।
अधिक जानकारी के लिए कृपया आवेदन की सहायता टीम से संपर्क करें।
रनटाइम त्रुटि का पता लगाने का पता लगाने की तुलना में बेहतर है, लेकिन यह थोड़ा प्रोसेसर समय बर्बाद करता है, और शायद अधिक प्रोग्रामर समय। संकलन समय पर पता लगाने के साथ बेहतर है! और यदि आप C ++ 98 के साथ स्थानीय प्रकार के सरणियों का समर्थन नहीं करने के लिए खुश हैं, तो आप ऐसा कर सकते हैं:
#include <stddef.h>
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
#define N_ITEMS( array ) n_items( array )
इस परिभाषा को संकलित करते हुए, G ++ के साथ पहले पूर्ण कार्यक्रम में प्रतिस्थापित किया गया, मुझे मिल गया ...
M: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: फ़ंक्शन में 'शून्य प्रदर्शन ( अंतर int *)':
compile_time_detection.cpp: 14: त्रुटि 'n_items (const int * &)' में कॉल के लिए कोई मिलान फ़ंक्शन नहीं है।
M: \ count> _
यह कैसे काम करता है: सरणी को संदर्भ के द्वारा पारित किया जाता हैn_items
, और इसलिए यह पहले तत्व को इंगित करने के लिए क्षय नहीं करता है, और फ़ंक्शन केवल प्रकार द्वारा निर्दिष्ट तत्वों की संख्या वापस कर सकता है।
C ++ 11 के साथ आप इसका उपयोग स्थानीय प्रकार के सरणियों के लिए भी कर सकते हैं, और यह एक सरणी के तत्वों की संख्या खोजने के लिए सुरक्षित
C ++ मुहावरा है ।
5.4 सी ++ 11 और सी ++ 14 नुकसान: एक constexpr
सरणी आकार फ़ंक्शन का उपयोग करना ।
C ++ 11 के साथ और बाद में यह स्वाभाविक है, लेकिन जैसा कि आप खतरनाक देखेंगे!, C ++ 03 फ़ंक्शन को बदलने के लिए
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
साथ में
using Size = ptrdiff_t;
template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }
जहां महत्वपूर्ण परिवर्तन का उपयोग होता है constexpr
, जो इस फ़ंक्शन को एक संकलन समय स्थिर बनाने की अनुमति देता है ।
उदाहरण के लिए, C ++ 03 फ़ंक्शन के विपरीत, इस तरह के एक संकलन समय का उपयोग उसी आकार के एक सरणी को घोषित करने के लिए किया जा सकता है:
// Example 1
void foo()
{
int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
constexpr Size n = n_items( x );
int y[n] = {};
// Using y here.
}
लेकिन constexpr
संस्करण का उपयोग करके इस कोड पर विचार करें :
// Example 2
template< class Collection >
void foo( Collection const& c )
{
constexpr int n = n_items( c ); // Not in C++14!
// Use c here
}
auto main() -> int
{
int x[42];
foo( x );
}
ख़तरा: जुलाई 2015 तक उपरोक्त मिगग 64 -.0.0 के साथ संकलित है
-pedantic-errors
, और, gcc.godbolt.org/ पर ऑनलाइन संकलक के साथ परीक्षण करना, साथ ही क्लैंग 3.0 और क्लैंग 3.2 के साथ, लेकिन क्लैंग 3.3, 3.4 के साथ नहीं। 1, 3.5.0, 3.5.1, 3.6 (आरसी 1) या 3.7 (प्रयोगात्मक)। और विंडोज प्लेटफॉर्म के लिए महत्वपूर्ण है, यह विजुअल C ++ 2015 के साथ संकलित नहीं होता है। इसका कारण संदर्भों के उपयोग के बारे में C ++ 11 / C ++ 14 स्टेटमेंट है।constexpr
भावों :
सी ++ 11 सी ++ 14 $ 5.19 / 2 नौ
वें डैश
एक सशर्त-अभिव्यक्ति e
एक मुख्य स्थिर अभिव्यक्ति है जब तक कि मूल्यांकन e
, अमूर्त मशीन (1.9) के नियमों का पालन करते हुए, निम्न अभिव्यक्तियों में से एक का मूल्यांकन करेगा:
is
- एक आईडी-एक्सप्रेशन, जो संदर्भ प्रकार के एक चर या डेटा सदस्य को संदर्भित करता है जब तक कि संदर्भ में पूर्ववर्ती प्रारंभ न हो और या तो
- यह एक स्थिर अभिव्यक्ति या के साथ आरंभिक है
- यह किसी वस्तु का गैर-स्थैतिक डेटा सदस्य है जिसका जीवनकाल ई के मूल्यांकन के दौरान शुरू हुआ था;
एक हमेशा अधिक क्रिया लिख सकता है
// Example 3 -- limited
using Size = ptrdiff_t;
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = std::extent< decltype( c ) >::value;
// Use c here
}
... लेकिन यह तब विफल हो जाता है जब Collection
कच्चा सरणी नहीं होता है।
गैर-सरणियों वाले संग्रह से निपटने के लिए किसी n_items
फ़ंक्शन की अधिभारता की आवश्यकता
होती है, लेकिन साथ ही, संकलन समय के लिए किसी को सरणी आकार के संकलन समय प्रतिनिधित्व की आवश्यकता होती है। और क्लासिक सी ++ 03 समाधान, जो सी ++ 11 और सी ++ 14 में भी ठीक काम करता है, फ़ंक्शन को उसके परिणाम को मान के रूप में नहीं बल्कि इसके फ़ंक्शन परिणाम प्रकार के माध्यम से रिपोर्ट करने देना है । इस तरह के उदाहरण के लिए:
// Example 4 - OK (not ideal, but portable and safe)
#include <array>
#include <stddef.h>
using Size = ptrdiff_t;
template< Size n >
struct Size_carrier
{
char sizer[n];
};
template< class Type, Size n >
auto static_n_items( Type (&)[n] )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
template< class Type, size_t n > // size_t for g++
auto static_n_items( std::array<Type, n> const& )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
#define STATIC_N_ITEMS( c ) \
static_cast<Size>( sizeof( static_n_items( c ).sizer ) )
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = STATIC_N_ITEMS( c );
// Use c here
(void) c;
}
auto main() -> int
{
int x[42];
std::array<int, 43> y;
foo( x );
foo( y );
}
के लिए रिटर्न प्रकार की पसंद के बारे में static_n_items
: इस कोड का उपयोग नहीं किया जाता है std::integral_constant
क्योंकि std::integral_constant
परिणाम सीधे constexpr
मूल्य के रूप में दर्शाया जाता है , मूल समस्या को फिर से प्रस्तुत करता है। एक Size_carrier
वर्ग के बजाय एक फ़ंक्शन को सीधे किसी सरणी में संदर्भ वापस कर सकता है। हालाँकि, हर कोई उस वाक्य रचना से परिचित नहीं है।
नामकरण के बारे में: इस समाधान का हिस्सा है constexpr
-invalid- नियत-से-संदर्भ समस्या के लिए संकलन के विकल्प को लगातार स्पष्ट करना है।
उम्मीद है कि oops-there-was-a-reference-to-in-your- constexpr
issue C ++ 17 के साथ तय किया जाएगा, लेकिन तब तक STATIC_N_ITEMS
उपरोक्त मैक्रो पोर्टेबिलिटी जैसे एक मैक्रो , जैसे क्लैंग और विजुअल C ++ कंपाइलर, रिटेनर टाइप सुरक्षा।
संबंधित: मैक्रोज़ स्कोप का सम्मान नहीं करते हैं, इसलिए नाम टकराव से बचने के लिए एक उपसर्ग नाम का उपयोग करना एक अच्छा विचार हो सकता है, जैसे MYLIB_STATIC_N_ITEMS
।