हेडर फाईल्स बद्दल बोलू काही तरी ...
जसा जसा प्रोग्रॅम मोठा मोठा होत जातो आणि आपणही लागल्या तशा फाईल्स include करत जातो, तसा तसा आपल्याला प्रत्येक प्रोग्रॅम फाईल मध्ये forward declaration / signature लिहीत बसावं लागते आणि हे प्रत्येक फाईल मध्ये जाऊन शोधत बसा अति जिवावर येण्यासारखं काम आहे , त्यामुळे असं काही करता येणार नाही का कि सगळी forward declarations / signatures हि एकाच फाईल मध्ये कोंबता येईल आणि तीच फाईल सगळीकडे वापरता येईल ? तीच फाईल म्हणजे हेडर फाईल .h एक्स्टेंशन असलेली फाईल, यालाच काहीजण include फाईल म्हणतात. अशा काही फाईल्स च एक्स्टेंशन .hpp नि सुद्धा असतात , तर मग लगेच प्रश्न पडतो .h आणि .hpp फाईल्स मध्ये काय फरक ?? बघूया पुढे.
बाकीच्या source फाईल्स ला लागणाऱ्या declarations लिहून ठेवणं हाच हेडर फाईल्स चा उद्देश आहे.
standard library मधल्या header files :
खालील प्रोग्रॅम बघा :
#include <iostream> int main() { std::cout << "Hello, world!" << std::endl; return 0; }
हा प्रोग्रॅम "Hello, world!" console वर प्रिंट करेल. बघितलं तर या प्रोग्रॅम मध्ये आपण cout नावाचं काहीहि डिफाइन केलेलं नाही आहे , तर मग कंपायलर ला कस कळल कि cout काय आहे ते ? याच उत्तर cout हे "iostream" नावाच्या हेडर फाईल मध्ये declared केलेलं आहे. जेव्हा आपण #include <iostream> असं लिहू तेव्हा आपण कंपायलर ला सांगत असतो iostream नावाच्या हेडर फाईल मध्ये असलेले सगळी कन्टेन्ट इन्कलुडींग फाईल मध्ये टाक. त्यामुळेच आपल्या source फाईल मध्ये हेडर फाईल मध्ये असलेली सगळी declared fun आपण आरामशीर वापरू शकतो. एक गोष्ट नेहमी लक्षात ठेवा कि "हेडर फाईल्स मध्ये फक्त declarations असतात" मग लगेच प्रश्न पडतो कि त्याचा actual इम्पलेमेंटेड कोड कुठे आहे , तो इम्पलेमेंटेड कोड C वा C++ runtime support library मध्ये असतो. हाच कोड लिंकर लिंक करताना आपल्या कोड मध्ये चिपकवतो. त्यामुळेच कंपायलर नि तयार केलेल्या .obj फाईल चा size बघा आणि लिंकर नि लिंक केल्यावरचा साईझ बघा , फरक तुम्हालाच जाणवेल.
समजा iostream हेडर फाईल नसती तर तुम्ही काय केलं असत ? जेव्हा जेव्हा std::cout तुम्ही वापरल तेव्हा तेव्हा तुम्ही स्वतः coutचा कोड manually प्रत्येक फाईल वर टाकत बसला असता, हो असं केल्यावर तुमचा कोड आरामशीर चालला असता, आणि असच पाहिजे तेच पाहिजेल तेव्हाच पाहिजे तोच कोड जर आपण लिहिला तर केव्हाही उत्तम . प्रोग्रॅम खूपच मजबूत होईल म्हणजे पाहिजे नसलेला कचरा आपल्या executable सोबत लिंक होऊन येणार नाही. हे करण्यासाठी काय घ्यायचं काय नाही याच ज्ञान असं अत्यावश्यक आहे. तरीही सुरवात करणार्यांनी सरळ हेडर फाईल लावा, यश घ्या आणि मग हे असले प्रयोग करत बसा .. उचापती ... :P
असो आपण स्वतःची हेडर फाईल कशी लिहायची ते बघूया :
समजा आपल्याकडे २ source फाईल्स आहेत
add.cpp int add(int num1, int num2){ return num1+ num2; } main.cpp #include <iostream> int add(int num1, int num2); int main(){ std::cout << "sum of two number is :"<< add(10,20) << std::endl; return 0; }
इथे main.cpp compile होताना कंपायलर ला add काय आहे हे कळण्यासाठी आपण एक forward declaration वापरलं . जस आधी म्हटल्याप्रमाणे प्रत्येक function साठी प्रत्येक फाईल मध्ये forward declaration लिहीत बसणं आणि तेच function जर दुसऱ्या source फाईल मध्ये वापरायचे असल्यास त्या फाईल च्या वर सुद्धा forward declaration टाकत बसणं लवकरच किचकट होत जाते.
हेडर फाईल हे काम खूप सोप्प करते. हेडर फाईल मध्ये एकदा forward declaration लिहा आणि पाहिजे तितक्या source फाईल मध्ये वापरा तेही एका ओळीत #include <फाईल च नाव >
याचा अजून एक चांगला फायदा आहे समजा function चा prototype काही कारणास्तव बदला लागला म्हणजे एखादा parameter टाका लागला किंवा आहे तोच बदला लागला तर हेडर मध्ये बदल केला कि तो सगळीकडे रिफ्लेक्ट होईल. त्यामुळे प्रत्येक फाईल मध्ये जाऊन बदलत बसण्याचे कष्ट वाचेल :P (वाचलेल्या वेळात गोट्या खेळा :P).
स्वतःची हेडर फाईल तयार करणं खूपच सोपं आहे, हेडर फाईल दोन भागात बघितली तर पहिला भाग असतो तो म्हणजे header guard, या बद्दल सविस्तर बघूया header guard वर बोलूया काहीतरी मध्ये.. थोडक्यात सांगायचं झालं तर हेडर गार्ड हे हेडर फाईल एका source फाईल मध्ये एकापेक्षा जास्त वेळ #इम्पोर्ट होणार नाही याची काळजी घेते.
दुसरा भाग म्हणजे हेडर फाईल चा actual content ज्या मध्ये आपण सगळ्या functions साठीची सगळी declarations लिहू जेणेकरून सगळ्या source फाईल ला दिसेल अर्थातच कंपायलर ला .
चला आपण आपली add.h फाईल तयार करू
add.h
// पहिल्या काही ओली header guard ची सुरवात करतात. ADD_H या नावाच्या जागी कोणताही unique name
// चाललं असत परंतु convention प्रमाणे आपण हेडर फाईलचाच नाव टाकूया.
#ifndef ADD_H #define ADD_H // इथून actual content म्हणजेच declarations टाकायचे. int add(int x, int y); // function prototype for add.h -- इथे सेमी कोलन टाकायला विसरू नका // header guard चा शेवट #endif
आता हि फाईल आपल्या main.cpp मध्ये वापराची असेल तर इम्पोर्ट करावी लागेल
main.cpp
#include <iostream>#include "add.h"int main(){ std::cout << "sum of two number is :"<< add(10,20) << std::endl; return 0; }
आणि add.cpp आहे तशीच राहील
int add(int num1, int num2){ return num1+ num2; }
जेव्हा आपण main.cpp compile करू तेव्हा preprocesor #include "add.h" या लाईन ला add.h मधला कन्टेन्ट त्याच लाईन च्या जागी कॉपी करेल त्यामुळे add मेथड च proptotype as a forward declaration आपल्याला उपलब्ध होईल. आपला प्रोग्रॅम सरळ सरळ compile आणि link होईल.
टीप : जेव्हा आपण एखादी फाईल #include करतो , इम्पोर्ट करते वेळी त्या फाईल मधला सगळा कन्टेन्ट source फाईल मध्ये कॉपी केल्या जातो
जर तुम्हाला कंपायलर error आली कि add.h not being found तर परत तपासून पहा फाईल नाव आणि इम्पोर्ट मध्ये लिहिलेले नाव व्यवस्थित आहे कि नाही. नसल्यास कंपायलर बोंबलणारच. save करताना कदाचित “add” (no extension) किंवा “add.h.txt” किंवा “add.hpp” असं save झालं असेल परत नीट तपासून घ्या.
आणि जर लिंकर बोंबलत असेल कि add() not being defined मग add.cpp आपल्या प्रोजेक्ट मध्ये बरोबर include झाली का नाही ते तपासून पहा.
Angled brackets vs quotes
आता तुम्हाला नक्की पेच पडला असणार iostream साठी angled brackets का वापरले आणि add.h साठी double quotes का वापरले ? अँगल ब्रॅकेट कंपायलर ला सांगत असतो कि हि फाईल सिस्टिम डिरेक्टरी मधून घे म्हणजेच C++ च्या स्टॅंडर्ड लायब्ररी जिथे आहे तिथे. आणि डबल quote सांगत कि हि फाईल जिथे source फाईल आहे त्याच डिरेक्टरी मध्ये किंवा user नि स्पेसिफाइड केलेल्या path वर मिळेल.
नियम : अँगल ब्रॅकेट तेव्हा वापरायचे जेव्हा तुम्हाला कंपायलर सोबत आलेल्या हेडर फाईल इंपोर्ट करायच्या आणि डबल ब्रॅकेट तेव्हा वापरायचे जेव्हा तुम्हाला तुमची फाईल किंवा दुसरी एखादी फाईल इम्पोर्ट करायच्या असतील तेव्हा.
एक हेडर फाईल बाकी हेडर फाईल ला इम्पोर्ट करू शकतात परंतु आपण त्यावर अवलंबून नाही राहायचं , प्रत्येक वेळी ज्या ज्या लागतील त्या त्या फाईल्स आपण स्वतः इम्पोर्ट करायच्या, कारण असेही होऊ शकते या version यामध्ये एका फाईल नि स्वतःच इम्पोर्ट केली होती आणि दुसऱ्या मध्ये गायब झाली म्हणून अवलंबून राहण्यापेक्षा स्वतः केलेल बर.
नियम : प्रत्येक .cpp फाईल मध्ये ज्या ज्या कंपायलर ला लागतात त्या त्या हेडर फाईल #include करायच्या.
iostream ला .h extension का नाही आहे ?
हा प्रश्न सुरवातीला c झाल रे झालं आणि cpp ला सुरवात केली रे केली कि पहिलीच ओळ #include < iostream > बस इथेच सूरु होते
आयुष्य तेच आहे
अन हाच पेच आहे - संगीता जोशी पुणे यांच्या एका गझलेचा मतला :)
हि ओळ पहिली हा आपली सर्वांची पंचाईत होते. पहिलाच प्रश्न iostream ला .h extension गेलं कुठे ?
याच उत्तर सोप आहे पण सुरवातीला माहिती नसते कि iostream आणि iostream.h या दोन्ही वेग वेगळ्या फाईल्स आहेत. हे समजून घेण्यासाठी थोडा भूतकाळ आठवावा लागतो. हे खरेच आहे इतिहास माहित नसला तर काही खरं नाही...!
जेव्हा नुकताच C++ चा जन्म झाला तेव्हा standard runtime library मधल्या सर्व फाईल्स .h होत्या. हे सर्व सुरळीत सुरु होत आणि चांगल पण होत. सुरवातीला cout आणि cin हे iostream.h मधेच होते परंतु कालांतराने जेव्हा ANSI committee नि C++ च standardization केल तेव्हा त्यांनी runtime library मध्ये असलेली सर्व functions उचलून std namespace मध्ये टाकली (आणि ते चांगलच होत). जरी इथे चांगल्यासाठी केलं असलं तरी मोठा पेच पडला. जर सगळी च्या सगळी functions std namespace मध्ये टाकल्या गेली तर जुने प्रोग्रॅम चालतील कसे त्यांना cout cin सारखी अति महत्वाची functions दिसणारच नाही आणि उलट कोणताही जुना प्रोग्रॅम चालणार नाही. म्हणून त्यांनी जुनी लायब्ररी फाईल्स तशीच ठेवली #include <iostream.h> आणि नवीन std namespace मध्ये टाकलेल्या फाईल्स तयार केल्या त्याला एक्स्टेंशन नाही दिल्या गेलं. म्हणून आज आपण इम्पोर्ट करताना #include <iostream> असच करतो.
आज प्रोग्रॅम करताना हे लक्षात ठेवा कि जर स्टॅंडर्ड लायब्ररी मध्ये बिना एक्स्टेंशन वाली हेडर फाईल असल्यास बिनधास्त ती प्राधान्याने वापरा. असून सुद्धा जर तुम्ही .h वाली फाईल वापरत असाल तर मात्र तुम्ही deprecate झालेली functionality वापरत आहात (NOT OUTDATED , OLD THINGS NEVER GET OUTDATED) deprecate होणे म्हणजे त्याला आता कुठलाही सपोर्ट नाही परंतु ते अजूनही जून होत तसेच आहे किंवा राहील ,ते यापुढे upgrade होणार नाहीत.
बघायला गेलं तर अजूनही बऱ्याच लायब्ररी ज्या C पासून inherit झाल्यात आणि आजही त्या आपण खूप वापरतो, C++ यामध्ये अशा लायब्ररी c prefix पासून सुरु होतात उदा stdlib.h तयार झाली ती cstdlib मध्ये. naming collisions टाळण्यासाठी यातली सुद्धा functions std namespace मध्ये टाकल्या गेली. परंतु आपण आपल्या हेडर फाईल तयार करताना .h extension देऊन तयार करणार आहोत कारण आपण आपला कोड std namespace मध्ये टाकणार नाही आहोत.
नियम : .h एक्स्टेंशन असलेली लायब्ररी उपलब्ध असल्यास तीच वापरा , std namespace चा वापर करून त्यातली functions वापरा . .h version नाही आहे किंवा स्वतःची headers तयार करणार असाल तर .h वापरा
दुसऱ्या directories मधून फाईल्स Include करताना :
आता दुसरा पेच .. other directories मधून हेडर फाईल कशा इम्पोर्ट करायच्या ??
एक मार्ग (वाईट) आहे त्या फाईल चा रेलॅटिव्ह path #include line मध्ये टाकणे, उदा :
#include <iostream>
#include "headers/add.h"
#include "../abc/headers/sub.h"
या पद्धतीने जर आपण वापर केला तर आपण डिरेक्टरी structure च्या बंधनात अडकून राहतो परत डिरेक्टरी structure मध्ये बदल करता येत नाही आणि केला तर लगेच कंपायलर बोंबलतो हेडर फाईल सापडत नाही आहे म्हणून हि पद्धत लहान प्रोजेक्ट पूरती मर्यादित वापरता येईल.
यावरचा सोपा उपाय म्हणजे जर कमांड लाईन वापरून compile करत असाल तर अतिशय उत्तम कारण आपण कंपायलर ला आधीच सांगू शकतो तुला ह्या ह्या डिरेक्टरी मध्ये सुद्धा शोध घ्यायचा आहे. तसेच IDE असेल तर त्यात इकडे जा तिकडे जा असं शोधत compiler आणि linker या दोन्ही ठिकाणी आपल्याला हेडर फाईल जिथे आहे तो path स्वतः सांगा लागतो.
उदा :
Visual Studio असेल तर Solution Explorer मध्ये जा मग तिथे प्रोजेक्ट वर right क्लिक करा मग प्रॉपर्टी वर क्लिक करा मग “VC++ Directories” tab वर जा आणि तिथे “Include Directories” मध्ये आपली डिरेक्टरी टाका जिथे आपण हेडर फाईल्स ठेवल्या आहेत.
Code::Blocks असेल तर प्रोजेक्ट मेनू मध्ये जा मग “Build Options” सिलेक्ट करा मग “Search directories” tab वर जा मग इथे आपली डिरेक्टरी टाका जिथे आपण हेडर फाईल्स ठेवल्या आहेत.
कमांड लाईन : एका ओळीत काम फक्त -I option द्यायचा
उदा :
Visual Studio Developer Command Prompt
cl.exe /EHsc main.cpp /I "/source/includes"
g++
g++ -o main -I /source/includes main.cpp
या पद्धतीचा फायदा हाच कि तुम्ही डिरेक्टरी स्ट्रक्टर बदलू शकता , बदल केला तरी प्रत्येक फाईल मध्ये बदल न करता फक्त कमांड लाईन ला path बदल करायचा किंवा IDE setting बदलायच्या.
हेडर फाईल मध्ये function definitions ठेवू शकतो का ?
C किंवा C++ बोंबलणार तर नाही परंतु खरं सांगायचं तर नका ठेवू . का ?
जस मी वर सांगितलं कि हेडर फाईल चा कन्टेन्ट सगळा जसा च्या तसा #include लाईन ला रिप्लेस केल्या जातो कॉपी केल्या जातो . म्हणजेच आपण एक जरी function हेडर मध्ये लिहिलं आणि ती हेडर फाईल जर का खूप cpp फाईल मध्ये इम्पोर्ट असेल तर मात्र बोंबच बोंब होईल.
प्रोजेक्ट लहान असेल तर काही प्रश्न नाही परंतु मोठा असल्यास फायदा कमी अन तोटेच जास्त अशी गत होईल . म्हणजे काय कंपायलर अजून जास्त वेळ घेईल कंपायलेशन साठी. कोड ची साईझ वाढेल उलट executable ची साईझ बिनकामीच डबल ट्रिपल झाली असेल. जर आपण फक्त कोड फाईल मध्ये बदल केला तर तीच कोड फाईल आपल्याला compile करा लागेल परंतु जर एका हेडर फाईल मध्ये बदल केला आणि ती फाईल हजार कोड फाईल मध्ये include असेल तर मात्र हजार फाईलस सुद्द्धा सगळ्या compile झाल्याचं पाहिजे . एक लहान बदल सुद्धा हजार राडे करायला भाग पाडतो. म्हणून कधीही हेडर फाईल मध्ये function definitions ठेवू नका. फक्त declaration ठेवा .
Header file लिहिण्याचे best practices काय ?
स्वतःची हेडर फाईल तयार करताना खालील मुद्दे ध्यानात असू द्या :
- नेहमी हेडर गार्ड टाकत जा
- variables कधीही हेडर फाईल मध्ये टाकू नका , constants असेल तर काही प्रश्न नाही , हेडर फाईल कधीही फक्त declarations साठीच वापरावी
- function कधीही हेडर फाईल मध्ये ठेवू नका.
- प्रत्येक हेडर फाईल त्या त्या कामासाठीच ठेवा म्हणजे समजा add करणारी सगळी functions add.h मध्ये ठेवा , वजाबाकी करणारी वजाबाकी.h मध्ये ठेवा.
- हेडर फाईल ला नाव देताना शक्यतो ज्या क्लास फाईल साठी बनवली तेच नाव द्या जेणेकरून नंतर गोंधळ होणार नाही .
- #include करताना ज्या लागतात त्याच #include करा.
- .cpp फाईल ला कधीही #include करू नका .
Header guards वर बोलू काही तरी ..... येतो घेऊन लवकरच .... तोवर परत वाचा प्रिप्रोसेसर वर बोलू काही तरी ... हेडर फाईल्स बद्दल बोलू काही तरी ..:P