मुझे C ++ से नफरत करने वाले कई C प्रोग्रामर भी दिखाई देते हैं। धीरे-धीरे यह समझने में कि मुझे क्या अच्छा है और क्या बुरा लगता है, इसमें मुझे काफी समय (वर्ष) लगा। मुझे लगता है कि वाक्यांश का सबसे अच्छा तरीका यह है:
कम कोड, कोई रन-टाइम ओवरहेड, अधिक सुरक्षा नहीं।
हम जितना कम कोड लिखेंगे, उतना अच्छा होगा। यह उन सभी इंजीनियरों में जल्दी से स्पष्ट हो जाता है जो उत्कृष्टता के लिए प्रयास करते हैं। आप एक जगह पर एक बग को ठीक करते हैं, कई नहीं - आप एक बार एक एल्गोरिथ्म व्यक्त करते हैं, और इसे कई जगहों पर फिर से उपयोग करते हैं, आदि यूनानियों में भी एक कहावत है, प्राचीन स्पार्टन्स का पता लगाया: "कम शब्दों में कुछ कहने के लिए, मतलब कि तुम इसके बारे में समझदार हो ”। और इस तथ्य का तथ्य यह है कि जब सही तरीके से उपयोग किया जाता है , तो C ++ आपको सी की तुलना में बहुत कम कोड में खुद को व्यक्त करने की अनुमति देता है, बिना रनटाइम गति के, और अधिक सुरक्षित होने के नाते (यानी संकलन-समय पर अधिक त्रुटियों को पकड़ता है)।
यहां मेरे रेंडर से एक सरल उदाहरण दिया गया है : जब एक त्रिकोण की स्कैनलाइन में पिक्सेल मूल्यों को प्रक्षेपित किया जाता है। मुझे एक X निर्देशांक X1 से शुरू करना है, और एक X समन्वय x2 तक पहुंचना है (एक त्रिकोण के बाईं ओर से)। और प्रत्येक चरण के पार, प्रत्येक पिक्सेल के पार, मैं मूल्यों को प्रक्षेपित करना चाहता हूं।
जब मैं पिक्सेल तक पहुंचने वाले परिवेश प्रकाश को प्रक्षेपित करता हूं:
typedef struct tagPixelDataAmbient {
int x;
float ambientLight;
} PixelDataAmbient;
...
// inner loop
currentPixel.ambientLight += dv;
जब मैं रंग को प्रक्षेपित करता हूं (जिसे "गॉर्ड" छायांकन कहा जाता है, जहां "लाल", "हरा" और "नीला" फ़ील्ड प्रत्येक पिक्सेल पर एक चरण मान द्वारा प्रक्षेपित होते हैं):
typedef struct tagPixelDataGouraud {
int x;
float red;
float green;
float blue; // The RGB color interpolated per pixel
} PixelDataGouraud;
...
// inner loop
currentPixel.red += dred;
currentPixel.green += dgreen;
currentPixel.blue += dblue;
जब मैं "फोंग" छायांकन में प्रस्तुत करता हूं, तो मैं अब एक तीव्रता (परिवेश) या एक रंग (लाल / हरा / नीला) को प्रक्षेपित नहीं करता हूं - मैं एक सामान्य वेक्टर (एनएक्स, एनवाई, एनजेड) और प्रत्येक चरण पर प्रक्षेप करता हूं, मुझे फिर से करना होगा प्रक्षेपित सामान्य वेक्टर के आधार पर प्रकाश समीकरण को बढ़ाएं:
typedef struct tagPixelDataPhong {
int x;
float nX;
float nY;
float nZ; // The normal vector interpolated per pixel
} PixelDataPhong;
...
// inner loop
currentPixel.nX += dx;
currentPixel.nY += dy;
currentPixel.nZ += dz;
अब, सी प्रोग्रामर्स की पहली वृत्ति "हेक, मानों को प्रक्षेपित करने वाले तीन कार्यों को लिखने और उन्हें सेट मोड के आधार पर कॉल करने की होगी"। सबसे पहले, इसका मतलब है कि मुझे एक प्रकार की समस्या है - मैं किसके साथ काम करता हूं? क्या मेरे पिक्सेल PixelDataAmbient हैं? PixelDataGouraud? PixelDataPhong? ओह, रुको, कुशल सी प्रोग्रामर कहते हैं, एक संघ का उपयोग करें!
typedef union tagSuperPixel {
PixelDataAmbient a;
PixelDataGouraud g;
PixelDataPhong p;
} SuperPixel;
..और फिर, आपके पास एक फ़ंक्शन है ...
RasterizeTriangleScanline(
enum mode, // { ambient, gouraud, phong }
SuperPixel left,
SuperPixel right)
{
int i,j;
if (mode == ambient) {
// handle pixels as ambient...
int steps = right.a.x - left.a.x;
float dv = (right.a.ambientLight - left.a.ambientLight)/steps;
float currentIntensity = left.a.ambientLight;
for (i=left.a.x; i<right.a.x; i++) {
WorkOnPixelAmbient(i, dv);
currentIntensity+=dv;
}
} else if (mode == gouraud) {
// handle pixels as gouraud...
int steps = right.g.x - left.g.x;
float dred = (right.g.red - left.g.red)/steps;
float dgreen = (right.g.green - left.a.green)/steps;
float dblue = (right.g.blue - left.g.blue)/steps;
float currentRed = left.g.red;
float currentGreen = left.g.green;
float currentBlue = left.g.blue;
for (j=left.g.x; i<right.g.x; j++) {
WorkOnPixelGouraud(j, currentRed, currentBlue, currentGreen);
currentRed+=dred;
currentGreen+=dgreen;
currentBlue+=dblue;
}
...
क्या आप अराजकता में फिसलते हुए महसूस करते हैं?
सबसे पहले, एक टाइपो वह सब है जो मेरे कोड को क्रैश करने के लिए आवश्यक है, क्योंकि कंपाइलर मुझे फ़ंक्शन के "गौर्ड" अनुभाग में कभी नहीं रोकेंगे, वास्तव में ".a" तक पहुंचने के लिए। (परिवेश) मूल्य। सी प्रकार प्रणाली (जो कि संकलन के दौरान) द्वारा पकड़ा गया बग नहीं है, इसका मतलब है कि एक बग जो रन-टाइम पर प्रकट होता है, और डिबगिंग की आवश्यकता होगी। क्या आपने देखा कि मैं left.a.green
"डग्रीन" की गणना में पहुंच रहा हूं ? संकलक ने निश्चित रूप से आपको ऐसा नहीं बताया।
फिर, हर जगह पुनरावृत्ति होती है - for
लूप वहां कई बार होता है जैसे कि रेंडरिंग मोड हैं, हम "सही माइनस लेफ्ट बाई स्टेप्स द्वारा विभाजित" करते रहते हैं। बदसूरत, और त्रुटि-प्रवण। क्या आपने गौर किया कि मैं "मैं" का उपयोग गौर्ड लूप में करता हूं, जब मुझे "जे" का उपयोग करना चाहिए था? संकलक फिर से, मौन है।
अगर मोड के लिए / और / सीढ़ी के बारे में क्या? यदि मैं तीन सप्ताह में एक नया रेंडरिंग मोड जोड़ूँ तो क्या होगा? क्या मुझे अपने सभी कोड में "अगर मोड ==" सभी में नया मोड संभालना याद होगा?
अब उपरोक्त कुरूपता की तुलना करें, C ++ स्ट्रक्चर्स और एक टेम्प्लेट फ़ंक्शन के इस सेट के साथ:
struct CommonPixelData {
int x;
};
struct AmbientPixelData : CommonPixelData {
float ambientLight;
};
struct GouraudPixelData : CommonPixelData {
float red;
float green;
float blue; // The RGB color interpolated per pixel
};
struct PhongPixelData : CommonPixelData {
float nX;
float nY;
float nZ; // The normal vector interpolated per pixel
};
template <class PixelData>
RasterizeTriangleScanline(
PixelData left,
PixelData right)
{
PixelData interpolated = left;
PixelData step = right;
step -= left;
step /= int(right.x - left.x); // divide by pixel span
for(int i=left.x; i<right.x; i++) {
WorkOnPixel<PixelData>(interpolated);
interpolated += step;
}
}
अब ये देखिए। हम अब एक संघ प्रकार-सूप नहीं बनाते हैं: हमारे पास प्रत्येक मोड पर विशिष्ट प्रकार हैं। वे बेस क्लास ( CommonPixelData
) से विरासत में अपने सामान्य सामान ("x" फ़ील्ड) का फिर से उपयोग करते हैं । और टेम्पलेट कंपाइलर बनाता है (अर्थात, कोड-जनरेट) तीन अलग-अलग फंक्शन्स जो हमने खुद C में लिखे होते हैं, लेकिन एक ही समय में, टाइप्स के बारे में बहुत सख्त होते हैं!
टेम्प्लेट में हमारा लूप नासमझ नहीं हो सकता है और अमान्य क्षेत्रों तक नहीं पहुंच सकता है - यदि हम करते हैं तो कंपाइलर छाल देगा।
टेम्पलेट सामान्य कार्य करता है (लूप, प्रत्येक समय में "चरण" से बढ़ रहा है), और यह इस तरीके से कर सकता है कि बस रनटाइम त्रुटियों का कारण नहीं बन सकता है। प्रकार के अनुसार प्रक्षेप ( AmbientPixelData
, GouraudPixelData
, PhongPixelData
) के साथ किया जाता operator+=()
है कि हम structs में जोड़ना होगा - जो मूल रूप से हुक्म कैसे प्रत्येक प्रकार अंतर्वेशित है।
और क्या आप देखते हैं कि हमने WorkOnPixel <T> के साथ क्या किया? हम प्रति प्रकार अलग-अलग काम करना चाहते हैं? हम बस एक टेम्पलेट विशेषज्ञता कहते हैं:
void WorkOnPixel<AmbientPixelData>(AmbientPixelData& p)
{
// use the p.ambientLight field
}
void WorkOnPixel<GouraudPixelData>(GouraudPixelData& p)
{
// use the p.red/green/blue fields
}
वह है - कॉल करने का कार्य, प्रकार के आधार पर तय किया जाता है। संकलन-समय पर!
इसे फिर से समझने के लिए:
- हम कोड (टेम्पलेट के माध्यम से) को कम करते हैं, आम भागों का फिर से उपयोग करते हैं,
- हम बदसूरत हैक्स का उपयोग नहीं करते हैं, हम एक सख्त प्रकार की प्रणाली रखते हैं, ताकि कंपाइलर हर समय हमारी जांच कर सके।
- और सभी का सबसे अच्छा: जो कुछ भी हमने नहीं किया उसका कोई भी रनटाइम प्रभाव नहीं है। यह कोड JUST के बराबर C कोड के रूप में तेजी से चलेगा - वास्तव में, अगर C कोड विभिन्न
WorkOnPixel
संस्करणों को कॉल करने के लिए फ़ंक्शन पॉइंटर्स का उपयोग कर रहा था , C ++ कोड C की तुलना में तेजी से होगा, क्योंकि कंपाइलर टाइप-विशिष्ट WorkOnPixel
टेम्पलेट विशेषज्ञता को इनलाइन करेगा कहते हैं!
कम कोड, कोई रन-टाइम ओवरहेड, अधिक सुरक्षा नहीं।
क्या इसका मतलब यह है कि C ++ भाषाओं का सभी-और अंत वाला है? बिलकूल नही। आपको अभी भी व्यापार-नाप को मापना है। अज्ञानी लोग C ++ का उपयोग करेंगे जब उन्हें बैश / पर्ल / पायथन स्क्रिप्ट लिखनी चाहिए। ट्रिगर-हैप्पी C ++ newbies आभासी एकाधिक वंशानुक्रम के साथ गहरी नेस्टेड कक्षाएं बनाएंगे, इससे पहले कि आप उन्हें रोक सकें और उन्हें पैकिंग भेज सकें। वे यह महसूस करने से पहले जटिल बूस्ट मेटा-प्रोग्रामिंग का उपयोग करेंगे कि यह आवश्यक नहीं है। वे अभी भी उपयोग करेगा char*
, strcmp
और मैक्रो के बजाय std::string
और टेम्पलेट्स।
लेकिन यह कहता है कि इससे ज्यादा कुछ नहीं ... देखो कि तुम किसके साथ काम करते हो। अक्षम उपयोगकर्ताओं (नहीं, यहां तक कि जावा) से आपको ढालने के लिए कोई भाषा नहीं है।
C ++ का अध्ययन और उपयोग करते रहें - बस ओवरडिज़ाइन न करें।